blob: 3ee02e186ed0260d0a54ea024840d7e5e9e0ad09 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webid/request_service.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webid/federated_auth_disconnect_request.h"
#include "content/browser/webid/metrics.h"
#include "content/browser/webid/test/delegated_idp_network_request_manager.h"
#include "content/browser/webid/test/federated_auth_request_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_permission_delegate.h"
#include "content/browser/webid/webid_utils.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/login_metrics.h"
#include "content/public/browser/webid/identity_request_dialog_controller.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/fake_local_frame.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 "metrics.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_status_code.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/webid/login_status_account.h"
#include "third_party/blink/public/common/webid/login_status_options.h"
#include "ui/base/page_transition_types.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "url/gurl.h"
#include "url/origin.h"
using blink::mojom::FederatedAuthRequestResult;
using blink::mojom::RequestTokenStatus;
using ApiPermissionStatus =
content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using AuthRequestCallbackHelper =
content::FederatedAuthRequestRequestTokenCallbackHelper;
using DismissReason = content::IdentityRequestDialogController::DismissReason;
using FedCmEntry = ukm::builders::Blink_FedCm;
using FedCmIdpEntry = ukm::builders::Blink_FedCmIdp;
using FetchStatus = content::IdpNetworkRequestManager::FetchStatus;
using Field = content::IdentityRequestDialogDisclosureField;
using TokenError = content::IdentityCredentialTokenError;
using ParseStatus = content::IdpNetworkRequestManager::ParseStatus;
using TokenStatus = content::webid::RequestIdTokenStatus;
using LoginState = content::IdentityRequestAccount::LoginState;
using SignInMode = content::IdentityRequestAccount::SignInMode;
using SignInStateMatchStatus = content::webid::SignInStateMatchStatus;
using ErrorDialogType = content::IdpNetworkRequestManager::FedCmErrorDialogType;
using TokenResponseType =
content::IdpNetworkRequestManager::FedCmTokenResponseType;
using ErrorUrlType = content::IdpNetworkRequestManager::FedCmErrorUrlType;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Return;
using ::testing::StrictMock;
namespace content::webid {
namespace {
constexpr char kProviderUrlFull[] = "https://idp.example/fedcm.json";
constexpr char kRpUrl[] = "https://rp.example/";
constexpr char kRpOtherUrl[] = "https://rp.example/random/";
constexpr char kIdpUrl[] = "https://idp.example/";
constexpr char kAccountsEndpoint[] = "https://idp.example/accounts";
constexpr char kCrossOriginAccountsEndpoint[] = "https://idp2.example/accounts";
constexpr char kTokenEndpoint[] = "https://idp.example/token";
constexpr char kClientMetadataEndpoint[] =
"https://idp.example/client_metadata";
constexpr char kMetricsEndpoint[] = "https://idp.example/metrics";
constexpr char kIdpBrandIconUrl[] = "https://idp.example/icon";
constexpr char kIdpLoginUrl[] = "https://idp.example/login_url";
constexpr char kIdpDisconnectUrl[] = "https://idp.example/disconnect";
constexpr char kPrivacyPolicyUrl[] = "https://rp.example/pp";
constexpr char kTermsOfServiceUrl[] = "https://rp.example/tos";
constexpr char kRpBrandIconUrl[] = "https://rp.example/icon";
constexpr char kClientId[] = "client_id_123";
constexpr char kNonce[] = "nonce123";
constexpr char kAccountEmailNicolas[] = "nicolas@email.com";
constexpr char kAccountEmailPeter[] = "peter@email.com";
constexpr char kAccountEmailZach[] = "zach@email.com";
constexpr char kAccountId[] = "1234";
constexpr char kAccountIdNicolas[] = "nico_id";
constexpr char kAccountIdPeter[] = "peter_id";
constexpr char kAccountIdZach[] = "zach_id";
constexpr char kAccountPicture[] = "https://idp.example/profilepic";
constexpr char kAccountPicture404[] = "https://idp.example/404";
constexpr int kAccountPictureSize = 10;
constexpr char kEmail[] = "ken@idp.example";
constexpr char kDomainHint[] = "domain@corp.com";
constexpr char kOtherDomainHint[] = "other_domain@corp.com";
// Values will be added here as token introspection is implemented.
constexpr char kToken[] = "[not a real token]";
constexpr char kEmptyToken[] = "";
constexpr char kFilterNoMatchMessage[] =
"Accounts were received, but none matched the login hint, domain hint, "
"and/or account labels provided.";
static const std::vector<std::string> kDomainHintVector = {kDomainHint};
static const std::vector<std::string> kLabelVector = {"label"};
static const std::vector<std::string> kLoginHints = {kAccountId, kEmail};
static const std::vector<std::string> kNicolasHints = {kAccountIdNicolas,
kAccountEmailNicolas};
static const std::vector<std::string> kPeterHints = {kAccountIdPeter,
kAccountEmailPeter};
static const std::vector<std::string> kTwoDomainHints = {kDomainHint,
kOtherDomainHint};
static const std::vector<std::string> kZachHints = {kAccountIdZach,
kAccountEmailZach};
static std::vector<IdentityRequestAccountPtr> kMultipleAccounts;
static std::vector<IdentityRequestAccountPtr>
kMultipleAccountsWithHintsAndDomains;
static std::vector<IdentityRequestAccountPtr> kSingleAccount;
static std::vector<IdentityRequestAccountPtr> kSingleAccountWithDomainHint;
static std::vector<IdentityRequestAccountPtr> kSingleAccountWithHint;
static std::vector<IdentityRequestAccountPtr> kTwoAccounts;
static const std::set<std::string> kWellKnown{kProviderUrlFull};
struct IdentityProviderParameters {
const char* provider;
const char* client_id;
const char* nonce;
const char* login_hint;
const char* domain_hint;
std::optional<std::vector<std::string>> fields;
const char* params_json;
std::optional<std::string> type;
bool from_idp_registration_api = false;
};
// Parameters for a call to RequestToken.
struct RequestParameters {
std::vector<IdentityProviderParameters> identity_providers;
blink::mojom::RpContext rp_context;
blink::mojom::RpMode rp_mode;
};
// Expected return values from a call to RequestToken.
//
// DO NOT ADD NEW MEMBERS.
// Having a lot of members in RequestExpectations encourages bad test design.
// Specifically:
// - It encourages making the test harness more magic
// - It makes each test "test everything", making it really hard to determine
// at a later date what the test was actually testing.
struct RequestExpectations {
std::optional<RequestTokenStatus> return_status;
FederatedAuthRequestResult devtools_issue_status;
std::optional<std::string> standalone_console_message;
std::optional<std::string> selected_idp_config_url;
bool is_auto_selected{false};
};
// Mock configuration values for test.
struct MockClientIdConfiguration {
FetchStatus fetch_status;
std::string privacy_policy_url;
std::string terms_of_service_url;
std::string brand_icon_url;
};
struct MockWellKnown {
std::set<std::string> provider_urls;
FetchStatus fetch_status;
};
// Mock information returned from IdpNetworkRequestManager::FetchConfig().
struct MockConfig {
FetchStatus fetch_status;
std::string accounts_endpoint;
std::string token_endpoint;
std::string client_metadata_endpoint;
std::string metrics_endpoint;
std::string idp_login_url;
std::string disconnect_endpoint;
std::optional<SkColor> brand_background_color;
std::optional<SkColor> brand_text_color;
std::string requested_label;
std::vector<std::string> types;
};
struct MockIdpInfo {
MockWellKnown well_known;
MockConfig config;
MockClientIdConfiguration client_metadata;
FetchStatus accounts_response;
std::vector<IdentityRequestAccountPtr> accounts;
};
// Action on accounts dialog taken by TestDialogController. Does not indicate a
// test expectation.
enum class AccountsDialogAction {
kNone,
kClose,
kSelectFirstAccount,
kAddAccount,
};
// Action on IdP-sign-in-status-mismatch dialog taken by TestDialogController.
// Does not indicate a test expectation.
enum class IdpSigninStatusMismatchDialogAction {
kNone,
kClose,
kClosePopup,
};
// Action on error dialog taken by TestDialogController.
// Does not indicate a test expectation.
enum class ErrorDialogAction {
kNone,
kClose,
kSwipe,
kGotIt,
kMoreDetails,
};
// Action on loading dialog taken by TestDialogController.
// Does not indicate a test expectation.
enum class LoadingDialogAction {
kNone,
kClose,
};
struct MockConfiguration {
const char* token;
base::flat_map<std::string, MockIdpInfo> idp_info;
FetchStatus token_response;
bool delay_token_response;
AccountsDialogAction accounts_dialog_action;
IdpSigninStatusMismatchDialogAction idp_signin_status_mismatch_dialog_action;
ErrorDialogAction error_dialog_action;
LoadingDialogAction loading_dialog_action;
std::optional<GURL> continue_on;
MediationRequirement mediation_requirement = MediationRequirement::kOptional;
std::optional<TokenError> token_error;
TokenResponseType token_response_type = TokenResponseType::
kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
std::optional<ErrorDialogType> error_dialog_type;
std::optional<ErrorUrlType> error_url_type;
blink::mojom::RpMode rp_mode{blink::mojom::RpMode::kPassive};
bool suppressed_by_segmentation_platform{false};
};
static const MockClientIdConfiguration kDefaultClientMetadata{
{ParseStatus::kSuccess, net::HTTP_OK},
kPrivacyPolicyUrl,
kTermsOfServiceUrl,
kRpBrandIconUrl};
static const IdentityProviderParameters kDefaultIdentityProviderRequestOptions{
kProviderUrlFull, kClientId, kNonce, /*login_hint=*/"",
/*domain_hint=*/""};
static const RequestParameters kDefaultRequestParameters{
std::vector<IdentityProviderParameters>{
kDefaultIdentityProviderRequestOptions},
blink::mojom::RpContext::kSignIn, blink::mojom::RpMode::kPassive};
static MockIdpInfo kDefaultIdentityProviderInfo;
static MockIdpInfo kProviderTwoInfo;
static base::flat_map<std::string, MockIdpInfo> kSingleProviderInfo;
constexpr char kProviderTwoUrlFull[] = "https://idp2.example/fedcm.json";
static MockConfiguration kConfigurationValid;
static MockConfiguration kConfigurationMultiIdpValid;
static const RequestExpectations kExpectationSuccess{
RequestTokenStatus::kSuccess, FederatedAuthRequestResult::kSuccess,
/*standalone_console_message=*/std::nullopt, kProviderUrlFull};
static const RequestParameters kDefaultMultiIdpRequestParameters{
std::vector<IdentityProviderParameters>{
{kProviderUrlFull, kClientId, kNonce, /*login_hint=*/"",
/*domain_hint=*/""},
{kProviderTwoUrlFull, kClientId, kNonce, /*login_hint=*/"",
/*domain_hint=*/""}},
/*rp_context=*/blink::mojom::RpContext::kSignIn,
/*rp_mode=*/blink::mojom::RpMode::kPassive};
url::Origin OriginFromString(const std::string& url_string) {
return url::Origin::Create(GURL(url_string));
}
enum class FetchedEndpoint {
CONFIG,
CLIENT_METADATA,
ACCOUNTS,
TOKEN,
WELL_KNOWN,
PICTURE,
};
class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
public:
void SetTestConfig(const MockConfiguration& configuration) {
config_ = configuration;
}
void RunDelayedCallbacks() {
for (base::OnceClosure& delayed_callback : delayed_callbacks_) {
std::move(delayed_callback).Run();
}
delayed_callbacks_.clear();
}
void FetchWellKnown(const GURL& provider,
FetchWellKnownCallback callback) override {
++num_fetched_[FetchedEndpoint::WELL_KNOWN];
std::string provider_key = provider.spec();
IdpNetworkRequestManager::WellKnown well_known;
std::set<GURL> url_set(
config_.idp_info[provider_key].well_known.provider_urls.begin(),
config_.idp_info[provider_key].well_known.provider_urls.end());
well_known.provider_urls = std::move(url_set);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
config_.idp_info[provider_key].well_known.fetch_status,
well_known));
}
void FetchConfig(const GURL& provider,
blink::mojom::RpMode rp_mode,
int idp_brand_icon_ideal_size,
int idp_brand_icon_minimum_size,
FetchConfigCallback callback) override {
++num_fetched_[FetchedEndpoint::CONFIG];
std::string provider_key = provider.spec();
const MockConfig& config = config_.idp_info[provider_key].config;
IdpNetworkRequestManager::Endpoints endpoints;
endpoints.token = GURL(config.token_endpoint);
endpoints.accounts = GURL(config.accounts_endpoint);
endpoints.client_metadata = GURL(config.client_metadata_endpoint);
endpoints.metrics = GURL(config.metrics_endpoint);
endpoints.disconnect = GURL(config.disconnect_endpoint);
IdentityProviderMetadata idp_metadata;
idp_metadata.config_url = provider;
idp_metadata.idp_login_url = GURL(config.idp_login_url);
idp_metadata.brand_background_color = config.brand_background_color;
idp_metadata.brand_icon_url = GURL(kIdpBrandIconUrl);
idp_metadata.brand_text_color = config.brand_text_color;
idp_metadata.requested_label = config.requested_label;
idp_metadata.types = config.types;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
config_.idp_info[provider_key].config.fetch_status,
endpoints, idp_metadata));
}
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 {
++num_fetched_[FetchedEndpoint::CLIENT_METADATA];
// Find the info of the provider with the same client metadata endpoint.
MockIdpInfo info;
for (const auto& idp_info : config_.idp_info) {
info = idp_info.second;
if (GURL(info.config.client_metadata_endpoint) == endpoint) {
break;
}
}
IdpNetworkRequestManager::ClientMetadata client_metadata;
client_metadata.privacy_policy_url =
GURL(info.client_metadata.privacy_policy_url);
client_metadata.terms_of_service_url =
GURL(info.client_metadata.terms_of_service_url);
client_metadata.brand_icon_url = GURL(info.client_metadata.brand_icon_url);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), info.client_metadata.fetch_status,
client_metadata));
}
void SendAccountsRequest(const url::Origin& idp_origin,
const GURL& accounts_url,
const std::string& client_id,
AccountsRequestCallback callback) override {
++num_fetched_[FetchedEndpoint::ACCOUNTS];
// Find the info of the provider with the same accounts endpoint.
MockIdpInfo info;
for (const auto& idp_info : config_.idp_info) {
info = idp_info.second;
if (GURL(info.config.accounts_endpoint) == accounts_url) {
break;
}
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), info.accounts_response,
accounts_list_.empty() ? info.accounts
: accounts_list_));
}
void SendTokenRequest(
const GURL& token_url,
const std::string& account,
const std::string& url_encoded_post_data,
bool idp_blidness,
TokenRequestCallback callback,
ContinueOnCallback on_continue,
RecordErrorMetricsCallback record_error_metrics_callback) override {
++num_fetched_[FetchedEndpoint::TOKEN];
base::OnceCallback bound_record_error_metrics_callback = base::BindOnce(
std::move(record_error_metrics_callback), config_.token_response_type,
config_.error_dialog_type, config_.error_url_type);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(bound_record_error_metrics_callback));
if (config_.token_error) {
TokenResult result;
result.error = config_.token_error;
base::OnceCallback bound_callback =
base::BindOnce(std::move(callback), config_.token_response, result);
if (config_.delay_token_response) {
delayed_callbacks_.push_back(std::move(bound_callback));
} else {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(bound_callback));
}
return;
}
if (config_.continue_on) {
base::OnceCallback bound_callback =
base::BindOnce(std::move(on_continue), config_.token_response,
config_.continue_on.value());
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(bound_callback));
return;
}
std::string delivered_token =
config_.token_response.parse_status == ParseStatus::kSuccess
? config_.token
: std::string();
TokenResult result;
result.token = delivered_token;
base::OnceCallback bound_callback =
base::BindOnce(std::move(callback), config_.token_response, result);
if (config_.delay_token_response) {
delayed_callbacks_.push_back(std::move(bound_callback));
} else {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(bound_callback));
}
}
void DownloadAndDecodeImage(const GURL& url,
ImageCallback callback) override {
EXPECT_TRUE(url == GURL(kAccountPicture) ||
url == GURL(kAccountPicture404) ||
url == GURL(kRpBrandIconUrl) || url == GURL(kIdpBrandIconUrl))
<< url;
++num_fetched_[FetchedEndpoint::PICTURE];
if (url == GURL(kAccountPicture404)) {
std::move(callback).Run(gfx::Image());
} else {
std::move(callback).Run(gfx::test::CreateImage(kAccountPictureSize));
}
}
std::map<FetchedEndpoint, size_t> num_fetched_;
// If non-empty, the accounts endpoint will return this accounts list instead
// of the accounts list in the `config_`.
std::vector<IdentityRequestAccountPtr> accounts_list_;
protected:
MockConfiguration config_{kConfigurationValid};
std::vector<base::OnceClosure> delayed_callbacks_;
};
// TestIdpNetworkRequestManager subclass which checks the values of the method
// params when executing an endpoint request.
class IdpNetworkRequestManagerParamChecker
: public TestIdpNetworkRequestManager {
public:
void SetExpectations(const std::string& expected_client_id,
const std::string& expected_selected_account_id) {
expected_client_id_ = expected_client_id;
expected_selected_account_id_ = expected_selected_account_id;
}
void SetExpectedTokenPostData(
const std::string& expected_url_encoded_post_data) {
expected_url_encoded_post_data_ = expected_url_encoded_post_data;
}
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 {
if (expected_client_id_) {
EXPECT_EQ(expected_client_id_, client_id);
}
TestIdpNetworkRequestManager::FetchClientMetadata(
endpoint, client_id, rp_brand_icon_ideal_size,
rp_brand_icon_minimum_size, std::move(callback));
}
void SendAccountsRequest(const url::Origin& idp_origin,
const GURL& accounts_url,
const std::string& client_id,
AccountsRequestCallback callback) override {
if (expected_client_id_) {
EXPECT_EQ(expected_client_id_, client_id);
}
TestIdpNetworkRequestManager::SendAccountsRequest(
idp_origin, accounts_url, client_id, std::move(callback));
}
void SendTokenRequest(
const GURL& token_url,
const std::string& account,
const std::string& url_encoded_post_data,
bool idp_blindness,
TokenRequestCallback callback,
ContinueOnCallback on_continue,
RecordErrorMetricsCallback record_error_metrics_callback) override {
if (expected_selected_account_id_) {
EXPECT_EQ(expected_selected_account_id_, account);
}
if (expected_url_encoded_post_data_) {
EXPECT_EQ(expected_url_encoded_post_data_, url_encoded_post_data);
}
TestIdpNetworkRequestManager::SendTokenRequest(
token_url, account, url_encoded_post_data, idp_blindness,
std::move(callback), std::move(on_continue),
std::move(record_error_metrics_callback));
}
private:
std::optional<std::string> expected_client_id_;
std::optional<std::string> expected_selected_account_id_;
std::optional<std::string> expected_url_encoded_post_data_;
};
class TestDialogController
: public NiceMock<MockIdentityRequestDialogController> {
public:
struct State {
// State related to ShowAccountsDialog().
// The list of all accounts passed to the UI.
std::vector<IdentityRequestAccountPtr> all_accounts_for_display;
std::optional<IdentityRequestAccount::SignInMode> sign_in_mode;
blink::mojom::RpContext rp_context;
std::vector<IdentityRequestAccountPtr> new_accounts;
// The last seen background/text color from IdP metadata.
std::optional<SkColor> brand_background_color;
std::optional<SkColor> brand_text_color;
// State related to ShowFailureDialog().
size_t num_show_idp_signin_status_mismatch_dialog_requests{0u};
// State related to ShowErrorDialog().
bool did_show_error_dialog{false};
std::optional<TokenError> token_error;
// State related to ShowLoadingDialog().
bool did_show_loading_dialog{false};
// List of IDP strings for which a mismatch is shown in a test.
std::vector<std::string> displayed_mismatch_idps;
};
explicit TestDialogController(MockConfiguration config)
: accounts_dialog_action_(config.accounts_dialog_action),
idp_signin_status_mismatch_dialog_action_(
config.idp_signin_status_mismatch_dialog_action),
error_dialog_action_(config.error_dialog_action),
loading_dialog_action_(config.loading_dialog_action),
should_show_accounts_passive_dialog_(
!config.suppressed_by_segmentation_platform) {}
~TestDialogController() override = default;
TestDialogController(TestDialogController&) = delete;
TestDialogController& operator=(TestDialogController&) = delete;
void SetState(State* state) { state_ = state; }
void SetIdpSigninStatusMismatchDialogAction(
IdpSigninStatusMismatchDialogAction action) {
idp_signin_status_mismatch_dialog_action_ = action;
}
void ShouldShowAccountsPassiveDialog(
ShouldShowAccountsPassiveDialogCallback cb) override {
std::move(cb).Run(should_show_accounts_passive_dialog_);
}
bool ShowAccountsDialog(
content::RelyingPartyData rp_data,
const std::vector<IdentityProviderDataPtr>& idp_list,
const std::vector<IdentityRequestAccountPtr>& accounts,
blink::mojom::RpMode rp_mode,
const std::vector<IdentityRequestAccountPtr>& new_accounts,
IdentityRequestDialogController::AccountSelectionCallback on_selected,
IdentityRequestDialogController::LoginToIdPCallback on_add_account,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::AccountsDisplayedCallback
accounts_displayed_callback) override {
if (!state_) {
return false;
}
state_->all_accounts_for_display.clear();
// Auto reauthn should not call ShowAccountsDialog.
state_->sign_in_mode = SignInMode::kExplicit;
state_->rp_context = idp_list[0]->rp_context;
state_->new_accounts = new_accounts;
state_->all_accounts_for_display = accounts;
for (const auto& idp_data : idp_list) {
if (idp_data->has_login_status_mismatch) {
state_->displayed_mismatch_idps.push_back(idp_data->idp_for_display);
}
EXPECT_FALSE(idp_data->idp_metadata.brand_decoded_icon.IsEmpty());
state_->brand_background_color =
idp_data->idp_metadata.brand_background_color;
state_->brand_text_color = idp_data->idp_metadata.brand_text_color;
}
switch (accounts_dialog_action_) {
case AccountsDialogAction::kSelectFirstAccount: {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(on_selected),
accounts[0]->identity_provider->idp_metadata.config_url,
accounts[0]->id,
accounts[0]->idp_claimed_login_state.value_or(
accounts[0]->browser_trusted_login_state) ==
LoginState::kSignIn));
break;
}
case AccountsDialogAction::kClose:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(dismiss_callback),
DismissReason::kCloseButton));
break;
case AccountsDialogAction::kAddAccount:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(on_add_account),
idp_list[0]->idp_metadata.config_url,
idp_list[0]->idp_metadata.idp_login_url));
// Set `accounts_dialog_action_` such that subsequent calls will select
// the first account.
accounts_dialog_action_ = AccountsDialogAction::kSelectFirstAccount;
break;
case AccountsDialogAction::kNone:
break;
}
did_show_ui_ = true;
return true;
}
bool ShowFailureDialog(
const RelyingPartyData& rp_data,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
const IdentityProviderMetadata& idp_metadata,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::LoginToIdPCallback
identity_registry_callback) override {
if (!state_) {
return false;
}
state_->displayed_mismatch_idps.push_back(idp_for_display);
++state_->num_show_idp_signin_status_mismatch_dialog_requests;
switch (idp_signin_status_mismatch_dialog_action_) {
case IdpSigninStatusMismatchDialogAction::kClose:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(dismiss_callback),
DismissReason::kCloseButton));
break;
case IdpSigninStatusMismatchDialogAction::kClosePopup:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(dismiss_callback), DismissReason::kOther));
break;
case IdpSigninStatusMismatchDialogAction::kNone:
break;
}
did_show_ui_ = true;
return true;
}
bool ShowErrorDialog(
const RelyingPartyData& rp_data,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
const IdentityProviderMetadata& idp_metadata,
const std::optional<TokenError>& error,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::MoreDetailsCallback
more_details_callback) override {
if (!state_) {
return false;
}
state_->did_show_error_dialog = true;
state_->token_error = error;
switch (error_dialog_action_) {
case ErrorDialogAction::kClose:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(dismiss_callback),
DismissReason::kCloseButton));
break;
case ErrorDialogAction::kSwipe:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(dismiss_callback), DismissReason::kSwipe));
break;
case ErrorDialogAction::kGotIt:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(dismiss_callback),
DismissReason::kGotItButton));
break;
case ErrorDialogAction::kMoreDetails:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(more_details_callback)));
break;
case ErrorDialogAction::kNone:
break;
}
did_show_ui_ = true;
return true;
}
bool ShowLoadingDialog(const RelyingPartyData& rp_data,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
IdentityRequestDialogController::DismissCallback
dismiss_callback) override {
if (!state_) {
return false;
}
state_->did_show_loading_dialog = true;
switch (loading_dialog_action_) {
case LoadingDialogAction::kClose:
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(dismiss_callback),
DismissReason::kCloseButton));
break;
case LoadingDialogAction::kNone:
break;
}
return true;
}
bool ShowVerifyingDialog(
const content::RelyingPartyData& rp_data,
const scoped_refptr<IdentityProviderData>& idp_data,
const scoped_refptr<content::IdentityRequestAccount>& account,
content::IdentityRequestAccount::SignInMode sign_in_mode,
blink::mojom::RpMode rp_mode,
IdentityRequestDialogController::AccountsDisplayedCallback
accounts_displayed_callback) override {
if (!state_) {
return false;
}
state_->sign_in_mode = sign_in_mode;
state_->all_accounts_for_display = {account};
did_show_ui_ = true;
return true;
}
bool DidShowUi() const override { return did_show_ui_; }
base::WeakPtr<TestDialogController> AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
AccountsDialogAction accounts_dialog_action_{AccountsDialogAction::kNone};
private:
IdpSigninStatusMismatchDialogAction idp_signin_status_mismatch_dialog_action_{
IdpSigninStatusMismatchDialogAction::kNone};
ErrorDialogAction error_dialog_action_{ErrorDialogAction::kNone};
LoadingDialogAction loading_dialog_action_{LoadingDialogAction::kNone};
bool did_show_ui_ = false;
bool should_show_accounts_passive_dialog_;
// Pointer so that the state can be queried after RequestService
// destroys TestDialogController.
raw_ptr<State> state_;
base::WeakPtrFactory<TestDialogController> weak_ptr_factory_{this};
};
class TestApiPermissionDelegate : public MockApiPermissionDelegate {
public:
using PermissionOverride = std::pair<url::Origin, ApiPermissionStatus>;
PermissionOverride permission_override_ =
std::make_pair(url::Origin(), ApiPermissionStatus::GRANTED);
std::optional<std::pair<size_t, PermissionOverride>>
permission_override_for_nth_;
std::set<url::Origin> embargoed_origins_;
size_t api_invocation_counter{0};
ApiPermissionStatus GetApiPermissionStatus(
const url::Origin& origin) override {
++api_invocation_counter;
if (embargoed_origins_.count(origin)) {
return ApiPermissionStatus::BLOCKED_EMBARGO;
}
if (permission_override_for_nth_ &&
permission_override_for_nth_->first == api_invocation_counter) {
return (origin == permission_override_for_nth_->second.first)
? permission_override_for_nth_->second.second
: ApiPermissionStatus::GRANTED;
}
return (origin == permission_override_.first)
? permission_override_.second
: ApiPermissionStatus::GRANTED;
}
void RecordDismissAndEmbargo(const url::Origin& origin) override {
embargoed_origins_.insert(origin);
}
void RemoveEmbargoAndResetCounts(const url::Origin& origin) override {
embargoed_origins_.erase(origin);
}
};
class TestPermissionDelegate : public NiceMock<MockPermissionDelegate> {
public:
std::map<url::Origin, std::optional<bool>> idp_signin_statuses_;
std::map<url::Origin, std::vector<IdentityRequestAccountPtr>>
idp_account_profiles_;
TestPermissionDelegate() = default;
~TestPermissionDelegate() override = default;
TestPermissionDelegate(TestPermissionDelegate&) = delete;
TestPermissionDelegate& operator=(TestPermissionDelegate&) = delete;
std::optional<bool> GetIdpSigninStatus(
const url::Origin& idp_origin) override {
auto it = idp_signin_statuses_.find(idp_origin);
return (it != idp_signin_statuses_.end()) ? it->second : std::nullopt;
}
void SetIdpSigninStatus(
const url::Origin& idp_origin,
bool idp_signin_status,
base::optional_ref<const blink::common::webid::LoginStatusOptions>
options) override {
idp_signin_statuses_[idp_origin] = idp_signin_status;
// Call parent so that EXPECT_CALL() works.
NiceMock<MockPermissionDelegate>::SetIdpSigninStatus(
idp_origin, idp_signin_status, std::nullopt);
}
void SetIdpAccounts(
const url::Origin& idp_origin,
std::vector<IdentityRequestAccountPtr>& request_accounts) {
idp_account_profiles_[idp_origin] = std::move(request_accounts);
}
};
class TestAutoReauthnPermissionDelegate
: public MockAutoReauthnPermissionDelegate {
public:
std::set<url::Origin> embargoed_origins_;
void RecordEmbargoForAutoReauthn(const url::Origin& origin) override {
embargoed_origins_.insert(origin);
}
};
class TestIdentityRegistry : public NiceMock<MockIdentityRegistry> {
public:
bool notified_{false};
explicit TestIdentityRegistry(
content::WebContents* web_contents,
base::WeakPtr<IdentityRegistryDelegate> delegate,
const GURL& idp_config_url)
: NiceMock<MockIdentityRegistry>(web_contents, delegate, idp_config_url) {
}
void NotifyClose(const url::Origin& notifier_origin) override {
notified_ = true;
}
};
} // namespace
class RequestServiceTest : public RenderViewHostImplTestHarness {
protected:
explicit RequestServiceTest(std::string_view rp_url = kRpUrl)
: rp_url_(rp_url) {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
~RequestServiceTest() override = default;
void InitConstants() {
kSingleAccount = {base::MakeRefCounted<IdentityRequestAccount>(
kAccountId, // id
kEmail, // display_identifier
"Ken R. Example", // display_name
kEmail, // email
"Ken R. Example", // name
"Ken", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"kenr", // username
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>() // labels
)};
kDefaultIdentityProviderInfo = {
{kWellKnown, {ParseStatus::kSuccess, net::HTTP_OK}},
{{ParseStatus::kSuccess, net::HTTP_OK},
kAccountsEndpoint,
kTokenEndpoint,
kClientMetadataEndpoint,
kMetricsEndpoint,
kIdpLoginUrl,
kIdpDisconnectUrl,
/*brand_background_color=*/std::nullopt,
/*brand_text_color=*/std::nullopt},
kDefaultClientMetadata,
{ParseStatus::kSuccess, net::HTTP_OK},
kSingleAccount,
};
kSingleProviderInfo = {{kProviderUrlFull, kDefaultIdentityProviderInfo}};
kSingleAccountWithHint = {base::MakeRefCounted<IdentityRequestAccount>(
kAccountId, // id
kEmail, // display_identifier
"Ken R. Example", // display_name
kEmail, // email
"Ken R. Example", // name
"Ken", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"kenr", // username
kLoginHints, // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>() // labels
)};
kSingleAccountWithDomainHint = {
base::MakeRefCounted<IdentityRequestAccount>(
kAccountId, // id
kEmail, // display_identifier
"Ken R. Example", // display_name
kEmail, // email
"Ken R. Example", // name
"Ken", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"kenr", // username
std::vector<std::string>(), // login_hints
kDomainHintVector, // domain_hints
std::vector<std::string>() // labels
)};
kTwoAccounts = {base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdNicolas, // id
kAccountEmailNicolas, // display_identifier
"Nicolas P", // display_name
kAccountEmailNicolas, // email
"Nicolas P", // name
"Nicolas", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"npm", // username
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignUp // login_state
),
base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdZach, // id
"zach@email.com", // display_identifier
"Zachary T", // display_name
"zach@email.com", // email
"Zachary T", // name
"Zach", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"tanz", // username
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignUp // login_state
)};
kMultipleAccounts = {base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdNicolas, // id
kAccountEmailNicolas, // display_identifier
"Nicolas P", // display_name
kAccountEmailNicolas, // email
"Nicolas P", // name
"Nicolas", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"npm", // username
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignUp // login_state
),
base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdPeter, // id
kAccountEmailPeter, // display_identifier
"Peter K", // display_name
kAccountEmailPeter, // email
"Peter K", // name
"Peter", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"peter", // username
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignIn // login_state
),
base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdZach, // id
"zach@email.com", // display_identifier
"Zachary T", // display_name
"zach@email.com", // email
"Zachary T", // name
"Zach", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"zacht", // username
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignUp // login_state
)};
kMultipleAccountsWithHintsAndDomains = {
base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdNicolas, // id
kAccountEmailNicolas, // display_identifier
"Nicolas P", // display_name
kAccountEmailNicolas, // email
"Nicolas P", // name
"Nicolas", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"npm", // username
kNicolasHints, // login_hints
kDomainHintVector, // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignUp // login_state
),
base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdPeter, // id
kAccountEmailPeter, // display_identifier
"Peter K", // display_name
kAccountEmailPeter, // email
"Peter K", // name
"Peter", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"peterk", // username
kPeterHints, // login_hints
std::vector<std::string>(), // domain_hints
kLabelVector, // labels
LoginState::kSignIn // login_state
),
base::MakeRefCounted<IdentityRequestAccount>(
kAccountIdZach, // id
kAccountEmailZach, // display_identifier
"Zachary T", // display_name
kAccountEmailZach, // email
"Zachary T", // name
"Zach", // given_name
GURL(), // picture
"(650) 312-3223", // phone number
"zacht", // username
kZachHints, // login_hints
kTwoDomainHints, // domain_hints
std::vector<std::string>(), // labels
LoginState::kSignUp // login_state
)};
kProviderTwoInfo = {{{kProviderTwoUrlFull}},
{{ParseStatus::kSuccess, net::HTTP_OK},
"https://idp2.example/accounts",
"https://idp2.example/token",
"https://idp2.example/client_metadata",
"https://idp2.example/metrics",
"https://idp2.example/login_url",
"https://idp2.example/disconnect",
/*brand_background_color=*/std::nullopt,
/*brand_text_color=*/std::nullopt},
kDefaultClientMetadata,
{ParseStatus::kSuccess, net::HTTP_OK},
kMultipleAccounts};
kConfigurationValid = {kToken,
kSingleProviderInfo,
{ParseStatus::kSuccess, net::HTTP_OK},
/*delay_token_response=*/false,
AccountsDialogAction::kSelectFirstAccount,
IdpSigninStatusMismatchDialogAction::kNone,
ErrorDialogAction::kClose,
LoadingDialogAction::kNone};
kConfigurationMultiIdpValid = {
kToken,
{{kProviderUrlFull, kDefaultIdentityProviderInfo},
{kProviderTwoUrlFull, kProviderTwoInfo}},
{ParseStatus::kSuccess, net::HTTP_OK},
false /* delay_token_response */,
AccountsDialogAction::kSelectFirstAccount,
IdpSigninStatusMismatchDialogAction::kNone,
ErrorDialogAction::kClose,
LoadingDialogAction::kNone};
}
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
// Initialize the accounts and account-dependent constants on SetUp() to
// ensure they are initialized correctly in every test.
InitConstants();
test_api_permission_delegate_ =
std::make_unique<TestApiPermissionDelegate>();
test_permission_delegate_ = std::make_unique<TestPermissionDelegate>();
test_auto_reauthn_permission_delegate_ =
std::make_unique<TestAutoReauthnPermissionDelegate>();
test_identity_registry_ = std::make_unique<TestIdentityRegistry>(
web_contents(), /*delegate=*/nullptr, GURL(kIdpUrl));
auth_helper_ = std::make_unique<AuthRequestCallbackHelper>();
static_cast<TestWebContents*>(web_contents())
->NavigateAndCommit(GURL(rp_url_), ui::PAGE_TRANSITION_LINK);
federated_auth_request_impl_ = &RequestService::CreateForTesting(
*main_test_rfh(), test_api_permission_delegate_.get(),
test_auto_reauthn_permission_delegate_.get(),
test_permission_delegate_.get(), test_identity_registry_.get(),
request_remote_.BindNewPipeAndPassReceiver());
std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
std::make_unique<TestIdpNetworkRequestManager>();
SetNetworkRequestManager(std::move(network_request_manager));
}
void SetNetworkRequestManager(
std::unique_ptr<TestIdpNetworkRequestManager> manager) {
test_network_request_manager_ = std::move(manager);
// DelegatedIdpNetworkRequestManager is owned by
// |federated_auth_request_impl_|.
federated_auth_request_impl_->SetNetworkManagerForTests(
std::make_unique<DelegatedIdpNetworkRequestManager>(
test_network_request_manager_.get()));
}
// Sets the TestDialogController to be used for the next call of
// RunAuthTest().
void SetDialogController(
std::unique_ptr<TestDialogController> dialog_controller) {
custom_dialog_controller_ = std::move(dialog_controller);
}
void RunAuthTest(
const RequestParameters& request_parameters,
const RequestExpectations& expectations,
const MockConfiguration& configuration,
AuthRequestCallbackHelper* concurrent_auth_helper = nullptr) {
AuthRequestCallbackHelper* auth_helper =
concurrent_auth_helper ?: auth_helper_.get();
request_remote_.set_disconnect_handler(auth_helper->quit_closure());
RunAuthDontWaitForCallback(request_parameters, configuration, auth_helper);
WaitForCurrentAuthRequest(/*should_fast_forward=*/true, auth_helper);
CheckAuthExpectations(configuration, expectations, auth_helper);
}
void RunAuthDontWaitForCallback(
const RequestParameters& request_parameters,
const MockConfiguration& configuration,
AuthRequestCallbackHelper* auth_helper = nullptr) {
if (!custom_dialog_controller_) {
custom_dialog_controller_ =
std::make_unique<TestDialogController>(configuration);
}
dialog_controller_state_ = TestDialogController::State();
custom_dialog_controller_->SetState(&dialog_controller_state_);
federated_auth_request_impl_->SetDialogControllerForTests(
std::move(custom_dialog_controller_));
SetConfig(configuration);
// If multiple IdPs are received, add them to a single get call. Unittests
// for multiple get calls can be added later as needed.
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> idp_ptrs;
for (const auto& identity_provider :
request_parameters.identity_providers) {
blink::mojom::IdentityProviderRequestOptionsPtr options =
blink::mojom::IdentityProviderRequestOptions::New();
options->config = blink::mojom::IdentityProviderConfig::New();
options->config->config_url = GURL(identity_provider.provider);
options->config->client_id = identity_provider.client_id;
options->config->from_idp_registration_api =
identity_provider.from_idp_registration_api;
options->nonce = identity_provider.nonce;
options->login_hint = identity_provider.login_hint;
options->domain_hint = identity_provider.domain_hint;
options->fields = std::move(identity_provider.fields);
if (identity_provider.params_json) {
options->params_json = identity_provider.params_json;
}
if (identity_provider.type) {
options->config->type = *identity_provider.type;
}
idp_ptrs.push_back(std::move(options));
}
blink::mojom::IdentityProviderGetParametersPtr get_params =
blink::mojom::IdentityProviderGetParameters::New(
std::move(idp_ptrs), request_parameters.rp_context,
request_parameters.rp_mode);
std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
idp_get_params.push_back(std::move(get_params));
auth_helper = auth_helper ?: auth_helper_.get();
PerformAuthRequest(std::move(idp_get_params),
configuration.mediation_requirement, auth_helper);
}
void CheckAuthExpectations(const MockConfiguration& configuration,
const RequestExpectations& expectation,
AuthRequestCallbackHelper* auth_helper = nullptr) {
auth_helper = auth_helper ?: auth_helper_.get();
ASSERT_EQ(expectation.return_status, auth_helper->status());
if (expectation.return_status == RequestTokenStatus::kSuccess) {
EXPECT_EQ(configuration.token, auth_helper->token());
} else {
EXPECT_TRUE(auth_helper->token() == std::nullopt ||
auth_helper->token() == kEmptyToken);
}
if (expectation.return_status == RequestTokenStatus::kSuccess) {
EXPECT_TRUE(DidFetchWellKnownAndConfig());
if (!base::FeatureList::IsEnabled(features::kFedCmLightweightMode)) {
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
// FetchedEndpoint::CLIENT_METADATA is optional.
EXPECT_EQ(did_show_accounts_dialog(),
!expectation.is_auto_selected ||
configuration.rp_mode != blink::mojom::RpMode::kActive);
}
EXPECT_EQ(expectation.is_auto_selected, auth_helper->is_auto_selected());
EXPECT_EQ(expectation.selected_idp_config_url,
auth_helper->selected_idp_config_url());
if (expectation.devtools_issue_status !=
FederatedAuthRequestResult::kSuccess) {
int issue_count = main_test_rfh()->GetFederatedAuthRequestIssueCount(
expectation.devtools_issue_status);
EXPECT_LE(1, issue_count);
} else {
int issue_count =
main_test_rfh()->GetFederatedAuthRequestIssueCount(std::nullopt);
if (!expectation.standalone_console_message) {
EXPECT_EQ(0, issue_count);
} else {
EXPECT_GE(1, issue_count);
}
}
CheckConsoleMessages(expectation.devtools_issue_status,
expectation.standalone_console_message);
}
void SetConfig(const MockConfiguration& config) {
test_network_request_manager_->SetTestConfig(config);
}
void CheckConsoleMessages(
FederatedAuthRequestResult devtools_issue_status,
const std::optional<std::string>& standalone_console_message) {
std::vector<std::string> messages =
RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
bool did_expect_any_messages = false;
size_t expected_message_index = messages.size() - 1;
if (devtools_issue_status != FederatedAuthRequestResult::kSuccess) {
std::string expected_message =
GetConsoleErrorMessageFromResult(devtools_issue_status);
did_expect_any_messages = true;
ASSERT_GE(expected_message_index, 0u);
EXPECT_EQ(expected_message, messages[expected_message_index--]);
}
if (standalone_console_message) {
did_expect_any_messages = true;
ASSERT_EQ(expected_message_index, 0u);
EXPECT_EQ(*standalone_console_message, messages[0]);
}
if (!did_expect_any_messages) {
EXPECT_EQ(0u, messages.size());
}
}
void PerformAuthRequest(
std::vector<blink::mojom::IdentityProviderGetParametersPtr>
idp_get_params,
MediationRequirement mediation_requirement,
AuthRequestCallbackHelper* auth_helper = nullptr) {
auth_helper = auth_helper ?: auth_helper_.get();
request_remote_->RequestToken(std::move(idp_get_params),
mediation_requirement,
auth_helper->callback());
// Ensure that the request makes its way to RequestService.
request_remote_.FlushForTesting();
base::RunLoop().RunUntilIdle();
}
void WaitForCurrentAuthRequest(
bool should_fast_forward = true,
AuthRequestCallbackHelper* auth_helper = nullptr) {
auth_helper = auth_helper ?: auth_helper_.get();
request_remote_.set_disconnect_handler(auth_helper->quit_closure());
// Fast forward clock so that the pending
// RequestService::OnRejectRequest() task, if any, gets a
// chance to run.
if (should_fast_forward) {
task_environment()->FastForwardBy(base::Minutes(10));
}
auth_helper->WaitForCallback();
request_remote_.set_disconnect_handler(base::OnceClosure());
}
void CloseDialog() {
federated_auth_request_impl_->OnDialogDismissed(
IdentityRequestDialogController::DismissReason::kCloseButton);
}
void CompleteDisconnectRequest() {
std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
std::make_unique<TestIdpNetworkRequestManager>();
std::unique_ptr<Metrics> fedcm_metrics =
std::make_unique<Metrics>(main_test_rfh()->GetPageUkmSourceId());
blink::mojom::IdentityCredentialDisconnectOptionsPtr options =
blink::mojom::IdentityCredentialDisconnectOptions::New();
options->config = blink::mojom::IdentityProviderConfig::New();
options->config->config_url = GURL(kProviderUrlFull);
federated_auth_request_impl_->disconnect_request_ =
FederatedAuthDisconnectRequest::Create(
std::move(network_request_manager), test_permission_delegate_.get(),
main_test_rfh(), std::move(fedcm_metrics), std::move(options));
federated_auth_request_impl_->disconnect_request_->callback_ =
base::DoNothing();
federated_auth_request_impl_->disconnect_request_->Complete(
blink::mojom::DisconnectStatus::kSuccess, DisconnectStatus::kSuccess);
}
base::span<const IdentityRequestAccountPtr> all_accounts_for_display() const {
return dialog_controller_state_.all_accounts_for_display;
}
base::span<const IdentityRequestAccountPtr> new_accounts() const {
return dialog_controller_state_.new_accounts;
}
std::vector<std::string> displayed_mismatch_idps() const {
return dialog_controller_state_.displayed_mismatch_idps;
}
bool did_show_accounts_dialog() const {
return !all_accounts_for_display().empty();
}
bool did_show_idp_signin_status_mismatch_dialog() const {
return dialog_controller_state_
.num_show_idp_signin_status_mismatch_dialog_requests;
}
std::optional<SkColor> brand_background_color() const {
return dialog_controller_state_.brand_background_color;
}
std::optional<SkColor> brand_text_color() const {
return dialog_controller_state_.brand_text_color;
}
int CountNumLoginStateIsSignin() {
int num_sign_in_login_state = 0;
for (const auto& account : all_accounts_for_display()) {
if (account->idp_claimed_login_state.value_or(
account->browser_trusted_login_state) == LoginState::kSignIn) {
++num_sign_in_login_state;
}
}
return num_sign_in_login_state;
}
bool DidFetchAnyEndpoint() {
for (auto& [endpoint, num] : test_network_request_manager_->num_fetched_) {
if (num > 0) {
return true;
}
}
return false;
}
// Convenience method as WELL_KNOWN and CONFIG endpoints are fetched in
// parallel.
bool DidFetchWellKnownAndConfig() {
return DidFetch(FetchedEndpoint::WELL_KNOWN) &&
DidFetch(FetchedEndpoint::CONFIG);
}
bool DidFetch(FetchedEndpoint endpoint) { return NumFetched(endpoint) > 0u; }
size_t NumFetched(FetchedEndpoint endpoint) {
return test_network_request_manager_->num_fetched_[endpoint];
}
ukm::TestAutoSetUkmRecorder* ukm_recorder() { return ukm_recorder_.get(); }
void ExpectStatusMetrics(
TokenStatus status,
MediationRequirement requirement = MediationRequirement::kOptional) {
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.RequestIdToken",
status, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Status.MediationRequirement", requirement, 1);
if (status == RequestIdTokenStatus::kSuccessUsingTokenInHttpResponse ||
status == RequestIdTokenStatus::kSuccessUsingIdentityProviderResolve) {
histogram_tester_.ExpectUniqueSample(
kBrowserAssistedLoginTypeHistogram,
BrowserAssistedLoginType::kFedCmPassive, 1);
}
ExpectStatusUKMInternal(status, requirement, FedCmEntry::kEntryName);
ExpectStatusUKMInternal(status, requirement, FedCmIdpEntry::kEntryName);
}
void ExpectStatusUKMInternal(TokenStatus status,
MediationRequirement requirement,
const char* entry_name) {
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
ASSERT_FALSE(entries.empty())
<< "No " << entry_name << " entry was recorded";
// There are multiple types of metrics under the same FedCM UKM. We need to
// make sure that the metric only includes the expected one.
bool metric_found = false;
for (const ukm::mojom::UkmEntry* const entry : entries) {
const int64_t* metric =
ukm_recorder()->GetEntryMetric(entry, "Status.RequestIdToken");
if (!metric) {
continue;
}
EXPECT_FALSE(metric_found)
<< "Found more than one entry with Status.RequestIdToken in "
<< entry_name;
metric_found = true;
EXPECT_EQ(static_cast<int>(status), *metric)
<< "Unexpected status recorded in " << entry_name;
}
EXPECT_TRUE(metric_found)
<< "No Status.RequestIdToken entry was found in " << entry_name;
}
void ExpectUKMPresence(const std::string& metric_name) {
ExpectUKMPresenceInternal(metric_name, FedCmEntry::kEntryName);
ExpectUKMPresenceInternal(metric_name, FedCmIdpEntry::kEntryName);
}
void ExpectUKMPresenceInternal(const std::string& metric_name,
const char* entry_name) {
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
ASSERT_FALSE(entries.empty())
<< "No " << entry_name << " entry was recorded";
for (const ukm::mojom::UkmEntry* const entry : entries) {
if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
SUCCEED();
return;
}
}
FAIL() << "Expected UKM " << metric_name << " was not recorded in "
<< entry_name;
}
void ExpectNoUKMPresence(const std::string& metric_name) {
ExpectNoUKMPresenceInternal(metric_name, FedCmEntry::kEntryName);
ExpectNoUKMPresenceInternal(metric_name, FedCmIdpEntry::kEntryName);
}
void ExpectNoUKMPresenceInternal(const std::string& metric_name,
const char* entry_name) {
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
ASSERT_FALSE(entries.empty())
<< "No " << entry_name << " entry was recorded";
for (const ukm::mojom::UkmEntry* const entry : entries) {
if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
FAIL() << "Unexpectedly found " << metric_name << " UKM in "
<< entry_name;
}
}
SUCCEED();
}
void ExpectUKMCount(const std::string& metric_name,
const char* entry_name,
int expected_count) {
int count = 0;
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
for (const ukm::mojom::UkmEntry* const entry : entries) {
if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
++count;
}
}
EXPECT_EQ(count, expected_count) << "Did not find the expected count for "
<< metric_name << " in " << entry_name;
}
void ExpectUkmValue(const std::string& metric_name,
int expected_value,
int num_idps = 1) {
ExpectUkmValueInEntry(metric_name, FedCmEntry::kEntryName, expected_value);
ExpectUkmValueInEntry(metric_name, FedCmIdpEntry::kEntryName,
expected_value, num_idps);
}
void ExpectUkmValueInEntry(const std::string& metric_name,
const char* entry_name,
int expected_value,
int expected_count = 1,
bool other_values_allowed = false) {
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;
if (!other_values_allowed) {
EXPECT_EQ(*value, expected_value);
}
}
EXPECT_EQ(count, expected_count);
EXPECT_GT(count, 0) << "Did not find " << metric_name << " in "
<< entry_name;
}
void ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus status) {
ExpectSignInStateMatchStatusUKMInternal(status, FedCmEntry::kEntryName);
ExpectSignInStateMatchStatusUKMInternal(status, FedCmIdpEntry::kEntryName);
}
void ExpectSignInStateMatchStatusUKMInternal(SignInStateMatchStatus status,
const char* entry_name) {
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
ASSERT_FALSE(entries.empty()) << "No FedCm entry was recorded";
// There are multiple types of metrics under the same FedCM UKM. We need to
// make sure that the metric only includes the expected one.
bool metric_found = false;
for (const ukm::mojom::UkmEntry* const entry : entries) {
const int64_t* metric =
ukm_recorder()->GetEntryMetric(entry, "Status.SignInStateMatch");
if (!metric) {
continue;
}
EXPECT_FALSE(metric_found)
<< "Found more than one Status.SignInStateMatch";
metric_found = true;
EXPECT_EQ(static_cast<int>(status), *metric);
}
EXPECT_TRUE(metric_found) << "No Status.SignInStateMatch was found";
}
void ExpectAutoReauthnMetrics(
std::optional<Metrics::NumAccounts> expected_returning_accounts,
bool expected_succeeded,
bool expected_auto_reauthn_setting_blocked,
bool expected_auto_reauthn_embargoed,
bool expected_prevent_silent_access) {
// UMA checks
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
expected_succeeded, 1);
if (expected_returning_accounts.has_value()) {
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AutoReauthn.ReturningAccounts",
static_cast<int>(*expected_returning_accounts), 1);
} else {
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.AutoReauthn.ReturningAccounts", 0);
}
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AutoReauthn.BlockedByContentSettings",
expected_auto_reauthn_setting_blocked, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AutoReauthn.BlockedByEmbargo",
expected_auto_reauthn_embargoed, 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.AutoReauthn.TimeFromEmbargoWhenBlocked",
expected_auto_reauthn_embargoed ? 1 : 0);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AutoReauthn.BlockedByPreventSilentAccess",
expected_prevent_silent_access, 1);
if (expected_succeeded) {
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.VerifyingDialogResult",
VerifyingDialogResult::kSuccessAutoReauthn, 1);
}
// UKM checks
auto entries = ukm_recorder()->GetEntriesByName(FedCmEntry::kEntryName);
ASSERT_FALSE(entries.empty()) << "No FedCM UKM entry was found!";
bool metric_found = false;
for (const ukm::mojom::UkmEntry* entry : entries) {
const int64_t* metric =
ukm_recorder()->GetEntryMetric(entry, "AutoReauthn.Succeeded");
if (!metric) {
EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.ReturningAccounts"));
EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.BlockedByContentSettings"));
EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.BlockedByEmbargo"));
EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.TimeFromEmbargoWhenBlocked"));
EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.BlockedByPreventSilentAccess"));
continue;
}
EXPECT_FALSE(metric_found) << "Found more than one AutoReauthn entry";
metric_found = true;
EXPECT_EQ(expected_succeeded, *metric);
metric = ukm_recorder()->GetEntryMetric(entry,
"AutoReauthn.ReturningAccounts");
if (expected_returning_accounts) {
ASSERT_TRUE(metric) << "AutoReauthn.ReturningAccounts was not found";
EXPECT_EQ(static_cast<int>(*expected_returning_accounts), *metric);
} else {
EXPECT_FALSE(metric)
<< "AutoReauthn.ReturningAccounts should not have been recorded";
}
metric = ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.BlockedByContentSettings");
ASSERT_TRUE(metric)
<< "AutoReauthn.BlockedByContentSettings was not found";
EXPECT_EQ(expected_auto_reauthn_setting_blocked, *metric);
metric =
ukm_recorder()->GetEntryMetric(entry, "AutoReauthn.BlockedByEmbargo");
ASSERT_TRUE(metric) << "AutoReauthn.BlockedByEmbargo was not found";
EXPECT_EQ(expected_auto_reauthn_embargoed, *metric);
metric = ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.TimeFromEmbargoWhenBlocked");
EXPECT_EQ(expected_auto_reauthn_embargoed, !!metric);
metric = ukm_recorder()->GetEntryMetric(
entry, "AutoReauthn.BlockedByPreventSilentAccess");
ASSERT_TRUE(metric)
<< "AutoReauthn.BlockedByPreventSilentAccess was not found";
EXPECT_EQ(expected_prevent_silent_access, *metric);
}
EXPECT_TRUE(metric_found) << "Did not find AutoReauthn metrics";
if (expected_succeeded) {
ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("VerifyingDialogResult");
}
}
void CheckAllFedCmSessionIDs(size_t expected_num_session_ids = 1u,
bool check_request_id_token = false) {
auto CheckUKMSessionID = [&](const auto& ukm_entries) {
std::set<int> session_ids;
std::set<int> session_ids_with_request_id_token;
ASSERT_FALSE(ukm_entries.empty());
for (const ukm::mojom::UkmEntry* const entry : ukm_entries) {
const auto* metric =
ukm_recorder()->GetEntryMetric(entry, "NumRequestsPerDocument");
if (metric) {
continue;
}
metric = ukm_recorder()->GetEntryMetric(entry, "FedCmSessionID");
ASSERT_TRUE(metric)
<< "All UKM events except for NumRequestsPerDocument should have "
"the SessionID metric";
int session_id = *metric;
session_ids.insert(session_id);
metric = ukm_recorder()->GetEntryMetric(entry, "Status.RequestIdToken");
if (!metric || !check_request_id_token) {
continue;
}
ASSERT_FALSE(session_ids_with_request_id_token.contains(session_id))
<< "A single session ID should only have one RequestIdToken";
session_ids_with_request_id_token.insert(session_id);
}
EXPECT_EQ(session_ids.size(), expected_num_session_ids);
if (check_request_id_token) {
EXPECT_EQ(session_ids_with_request_id_token.size(),
expected_num_session_ids);
}
};
CheckUKMSessionID(ukm_recorder()->GetEntriesByName(FedCmEntry::kEntryName));
CheckUKMSessionID(
ukm_recorder()->GetEntriesByName(FedCmIdpEntry::kEntryName));
}
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr>
MaybeAddRegisteredProviders(
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr>& providers) {
return federated_auth_request_impl_->MaybeAddRegisteredProviders(providers);
}
void ExpectTwoUniqueSessionIDs() {
auto CheckSessionIDs([&](const char* entry_name) {
std::vector<int64_t> session_ids =
ukm_recorder()->GetMetricsEntryValues(entry_name, "FedCmSessionID");
ASSERT_EQ(2, session_ids.size());
ASSERT_NE(session_ids[0], session_ids[1]);
});
CheckSessionIDs(FedCmEntry::kEntryName);
CheckSessionIDs(FedCmIdpEntry::kEntryName);
}
blink::mojom::IdentityProviderRequestOptionsPtr
NewNamedIdP(GURL config_url, std::string client_id, bool is_registered) {
blink::mojom::IdentityProviderRequestOptionsPtr options =
blink::mojom::IdentityProviderRequestOptions::New();
blink::mojom::IdentityProviderConfigPtr config =
blink::mojom::IdentityProviderConfig::New();
config->config_url = config_url;
config->client_id = client_id;
options->config = std::move(config);
options->config->from_idp_registration_api = is_registered;
return options;
}
blink::mojom::IdentityProviderRequestOptionsPtr NewRegisteredIdP(
std::string client_id) {
blink::mojom::IdentityProviderConfigPtr config =
blink::mojom::IdentityProviderConfig::New();
config->from_idp_registration_api = true;
config->client_id = client_id;
blink::mojom::IdentityProviderRequestOptionsPtr options =
blink::mojom::IdentityProviderRequestOptions::New();
options->config = std::move(config);
return options;
}
blink::mojom::IdentityProviderRequestOptionsPtr NewIDPWithFields(
const std::optional<std::vector<std::string>>& fields) {
blink::mojom::IdentityProviderRequestOptionsPtr options =
blink::mojom::IdentityProviderRequestOptions::New();
blink::mojom::IdentityProviderConfigPtr config =
blink::mojom::IdentityProviderConfig::New();
config->config_url = GURL(kProviderUrlFull);
config->client_id = "";
options->config = std::move(config);
options->fields = fields;
return options;
}
void SimulateLoginToIdP(std::string login_url = kIdpLoginUrl) {
federated_auth_request_impl_->LoginToIdP(/*can_append_hints=*/true,
GURL(kIdpUrl), GURL(login_url));
}
void ExpectSuccessfulActiveFlow() {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
// Expect a modal dialog to be opened to sign-in to the IdP.
std::unique_ptr<WebContents> modal(CreateTestWebContents());
base::RunLoop loop;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &loop](const GURL& url) {
loop.Quit();
return modal.get();
}));
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
request_remote_.set_disconnect_handler(auth_helper_->quit_closure());
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RunAuthDontWaitForCallback(parameters, kConfigurationValid);
loop.Run();
// When the modal dialog is opened, emulate the user signing-in by
// updating the internal sign-in status state and notifying the
// observers.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
federated_auth_request_impl_->OnIdpSigninStatusReceived(
OriginFromString(kProviderUrlFull), true);
WaitForCurrentAuthRequest(/*should_fast_forward=*/false);
CheckAuthExpectations(kConfigurationValid, kExpectationSuccess);
// These metrics are not recorded when a user's LoginStatus is "logged-out"
// such that they need to sign in to the IdP in the active flow.
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown."
"WellKnownAndConfigFetch",
0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch",
0);
histogram_tester_.ExpectUniqueSample(kBrowserAssistedLoginTypeHistogram,
BrowserAssistedLoginType::kFedCmActive,
1);
}
protected:
std::string rp_url_;
mojo::Remote<blink::mojom::FederatedAuthRequest> request_remote_;
raw_ptr<RequestService, AcrossTasksDanglingUntriaged>
federated_auth_request_impl_;
std::unique_ptr<TestIdpNetworkRequestManager> test_network_request_manager_;
std::unique_ptr<TestApiPermissionDelegate> test_api_permission_delegate_;
std::unique_ptr<TestPermissionDelegate> test_permission_delegate_;
std::unique_ptr<TestAutoReauthnPermissionDelegate>
test_auto_reauthn_permission_delegate_;
std::unique_ptr<TestIdentityRegistry> test_identity_registry_;
std::unique_ptr<AuthRequestCallbackHelper> auth_helper_;
// Enables test to inspect TestDialogController state after
// RequestService destroys TestDialogController. Recreated during
// each run of RunAuthTest().
TestDialogController::State dialog_controller_state_;
base::HistogramTester histogram_tester_;
private:
std::unique_ptr<TestDialogController> custom_dialog_controller_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};
// Test successful FedCM request.
TEST_F(RequestServiceTest, SuccessfulRequest) {
// Use IdpNetworkRequestManagerParamChecker to validate passed-in parameters
// to IdpNetworkRequestManager methods.
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectations(kClientId, kAccountId);
SetNetworkRequestManager(std::move(checker));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
// Check that client metadata is fetched. Using `kExpectationSuccess`
// expectation does not check that the client metadata was fetched because
// client metadata is optional.
EXPECT_TRUE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
}
// Test successful well-known fetching.
TEST_F(RequestServiceTest, WellKnownSuccess) {
// Use IdpNetworkRequestManagerParamChecker to validate passed-in parameters
// to IdpNetworkRequestManager methods.
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectations(kClientId, kAccountId);
SetNetworkRequestManager(std::move(checker));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
// Test the provider url is not in the well-known.
TEST_F(RequestServiceTest, WellKnownNotInList) {
RequestExpectations request_not_in_list = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigNotInWellKnown,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
const char* idp_config_url =
kDefaultRequestParameters.identity_providers[0].provider;
const char* kWellKnownMismatchConfigUrl = "https://mismatch.example";
EXPECT_NE(std::string(idp_config_url), kWellKnownMismatchConfigUrl);
MockConfiguration config = kConfigurationValid;
config.idp_info[idp_config_url].well_known = {
{kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
RunAuthTest(kDefaultRequestParameters, request_not_in_list, config);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
// Test that when the provider url is not in the well-known, it succeeds when it
// is registered.
TEST_F(RequestServiceTest, WellKnownNotInListButRegistered) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmIdPRegistration);
const char* idp_config_url =
kDefaultRequestParameters.identity_providers[0].provider;
const char* kWellKnownMismatchConfigUrl = "https://mismatch.example";
EXPECT_NE(std::string(idp_config_url), kWellKnownMismatchConfigUrl);
MockConfiguration config = kConfigurationValid;
config.idp_info[idp_config_url].well_known = {
{kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
RequestParameters requestParameters = kDefaultRequestParameters;
requestParameters.identity_providers[0].from_idp_registration_api = true;
// Need to simulate there is actually a registered IdP, or the call will fail.
std::vector<GURL> registry;
registry.emplace_back(kProviderUrlFull);
EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
.WillOnce(Return(registry));
RunAuthTest(requestParameters, kExpectationSuccess, config);
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
}
// Test that the well-known file has too many provider urls.
TEST_F(RequestServiceTest, WellKnownHasTooManyProviderUrls) {
RequestExpectations expectation = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kWellKnownTooBig,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration config = kConfigurationValid;
config.idp_info[kProviderUrlFull].well_known = {
{kProviderUrlFull, kProviderTwoUrlFull},
{ParseStatus::kSuccess, net::HTTP_OK}};
RunAuthTest(kDefaultRequestParameters, expectation, config);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
// Test that the well-known enforcement is bypassed.
TEST_F(RequestServiceTest, WellKnownEnforcementBypassed) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmWithoutWellKnownEnforcement);
MockConfiguration config = kConfigurationValid;
// The provider is not in the provider_list from the well-known.
config.idp_info[kProviderUrlFull].well_known = {
{kProviderTwoUrlFull}, {ParseStatus::kSuccess, net::HTTP_OK}};
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, config);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
// Test that not having the filename in the well-known fails.
TEST_F(RequestServiceTest, WellKnownHasNoFilename) {
MockConfiguration config{kConfigurationValid};
config.idp_info[kProviderUrlFull].well_known.provider_urls =
std::set<std::string>{GURL(kProviderUrlFull).GetWithoutFilename().spec()};
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigNotInWellKnown,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, config);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
// Test that request fails if config is missing token endpoint.
TEST_F(RequestServiceTest, MissingTokenEndpoint) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.token_endpoint = "";
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
std::vector<std::string> messages =
RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
ASSERT_EQ(2U, messages.size());
EXPECT_EQ(
"Config file is missing or has an invalid URL for the following:\n"
"\"id_assertion_endpoint\"\n",
messages[0]);
EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}
// Test that request fails if config is missing accounts endpoint.
TEST_F(RequestServiceTest, MissingAccountsEndpoint) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.accounts_endpoint = "";
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
std::vector<std::string> messages =
RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
ASSERT_EQ(2U, messages.size());
EXPECT_EQ(
"Config file is missing or has an invalid URL for the following:\n"
"\"accounts_endpoint\"\n",
messages[0]);
EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}
// Test that request fails if config is missing an IDP login URL.
TEST_F(RequestServiceTest, MissingLoginURL) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.idp_login_url = "";
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
std::vector<std::string> messages =
RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
ASSERT_EQ(2U, messages.size());
EXPECT_EQ(
"Config file is missing or has an invalid URL for the following:\n"
"\"login_url\"\n",
messages[0]);
EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}
// Test that client metadata endpoint is not required in config.
TEST_F(RequestServiceTest, MissingClientMetadataEndpoint) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.client_metadata_endpoint = "";
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
}
// Test that request fails if the accounts endpoint is in a different origin
// than identity provider.
TEST_F(RequestServiceTest, AccountEndpointDifferentOriginIdp) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.accounts_endpoint =
kCrossOriginAccountsEndpoint;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
// Test that request fails if IDP login URL is different origin from IDP config
// URL.
TEST_F(RequestServiceTest, LoginUrlDifferentOriginIdp) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.idp_login_url =
"https://idp2.example/login_url";
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
std::vector<std::string> messages =
RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
ASSERT_EQ(2U, messages.size());
EXPECT_EQ(
"Config file is missing or has an invalid URL for the following:\n"
"\"login_url\"\n",
messages[0]);
EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}
// Test that request fails if the idp is not https.
TEST_F(RequestServiceTest, ProviderNotTrustworthy) {
IdentityProviderParameters identity_provider{
"http://idp.example/fedcm.json", kClientId, kNonce, /*login_hint=*/"",
/*domain_hint=*/""};
RequestParameters request{
std::vector<IdentityProviderParameters>{identity_provider},
/*rp_context=*/blink::mojom::RpContext::kSignIn,
/*rp_mode=*/blink::mojom::RpMode::kPassive};
MockConfiguration configuration = kConfigurationValid;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdpNotPotentiallyTrustworthy,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(request, expectations, configuration);
EXPECT_FALSE(DidFetchAnyEndpoint());
ExpectStatusMetrics(TokenStatus::kIdpNotPotentiallyTrustworthy);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", false, 1);
ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, false);
}
// Test that request fails if accounts endpoint cannot be reached.
TEST_F(RequestServiceTest, AccountEndpointCannotBeReached) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kNoResponseError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsNoResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
}
// Test that request fails if account endpoint response cannot be parsed.
TEST_F(RequestServiceTest, AccountsCannotBeParsed) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.Raw", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
// Only records the following histograms if there are accounts to be shown.
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 0);
ExpectNoUKMPresence(
"Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch");
ExpectNoUKMPresence("Timing.ShowAccountsDialogBreakdown.AccountsFetch");
ExpectNoUKMPresence("Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch");
// Records the following histogram as long as the well-known and config file
// is fetched.
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.WellKnownAndConfigFetch", 1);
}
// Test that privacy policy, terms of service or RP brand icon URLs are not
// required in client metadata.
TEST_F(RequestServiceTest, ClientMetadataNoUrls) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].client_metadata =
kDefaultClientMetadata;
configuration.idp_info[kProviderUrlFull].client_metadata.privacy_policy_url =
"";
configuration.idp_info[kProviderUrlFull]
.client_metadata.terms_of_service_url = "";
configuration.idp_info[kProviderUrlFull].client_metadata.brand_icon_url = "";
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}
// Test that privacy policy URL is not required in client metadata.
TEST_F(RequestServiceTest, ClientMetadataNoPrivacyPolicyUrl) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].client_metadata =
kDefaultClientMetadata;
configuration.idp_info[kProviderUrlFull].client_metadata.privacy_policy_url =
"";
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}
// Test that terms of service URL is not required in client metadata.
TEST_F(RequestServiceTest, ClientMetadataNoTermsOfServiceUrl) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].client_metadata =
kDefaultClientMetadata;
configuration.idp_info[kProviderUrlFull]
.client_metadata.terms_of_service_url = "";
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}
// Test that RP brand icon URL is not required in client metadata.
TEST_F(RequestServiceTest, ClientMetadataNoRpBrandIconUrl) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].client_metadata =
kDefaultClientMetadata;
configuration.idp_info[kProviderUrlFull].client_metadata.brand_icon_url = "";
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}
// Test that request fails if all of the endpoints in the config are invalid.
TEST_F(RequestServiceTest, AllInvalidEndpoints) {
// Both an empty url and cross origin urls are invalid endpoints.
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.accounts_endpoint =
"https://cross-origin-1.com";
configuration.idp_info[kProviderUrlFull].config.token_endpoint = "";
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
std::vector<std::string> messages =
RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
ASSERT_EQ(2U, messages.size());
EXPECT_EQ(
"Config file is missing or has an invalid URL for the following:\n"
"\"id_assertion_endpoint\"\n"
"\"accounts_endpoint\"\n",
messages[0]);
EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}
// Tests for browser trusted login states.
TEST_F(RequestServiceTest,
BrowserTrustedLoginStateShouldBeSignUpForFirstTimeUser) {
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_EQ(LoginState::kSignUp,
all_accounts_for_display()[0]->browser_trusted_login_state);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasSigninAccount", false,
1);
}
TEST_F(RequestServiceTest,
BrowserTrustedLoginStateShouldBeSignInForReturningUser) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_EQ(LoginState::kSignIn,
all_accounts_for_display()[0]->browser_trusted_login_state);
// CLIENT_METADATA only needs to be fetched for obtaining links to display in
// the disclosure text. The disclosure text is not displayed for returning
// users, thus fetching the client metadata endpoint should be skipped.
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
histogram_tester_.ExpectUniqueTimeSample(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch",
base::TimeDelta(), 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasSigninAccount", true, 1);
}
TEST_F(RequestServiceTest, LoginStateSuccessfulSignUpGrantsSharingPermission) {
EXPECT_CALL(*test_permission_delegate_, GetLastUsedTimestamp(_, _, _, _))
.WillRepeatedly(Return(std::nullopt));
EXPECT_CALL(
*test_permission_delegate_,
GrantSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.Times(1);
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
TEST_F(RequestServiceTest, LoginStateFailedSignUpNotGrantSharingPermission) {
EXPECT_CALL(*test_permission_delegate_, GetLastUsedTimestamp(_, _, _, _))
.WillRepeatedly(Return(std::nullopt));
EXPECT_CALL(*test_permission_delegate_, GrantSharingPermission(_, _, _, _))
.Times(0);
MockConfiguration configuration = kConfigurationValid;
configuration.token_response.parse_status =
ParseStatus::kInvalidResponseError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
}
// Test that auto re-authn permission is not embargoed upon explicit sign-in.
TEST_F(RequestServiceTest, ExplicitSigninEmbargo) {
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
EXPECT_TRUE(
test_auto_reauthn_permission_delegate_->embargoed_origins_.empty());
}
// Test that auto re-authn permission is embargoed upon successful auto
// re-authn.
TEST_F(RequestServiceTest, AutoReauthnEmbargo) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
RequestExpectations expectation = kExpectationSuccess;
expectation.is_auto_selected = true;
RunAuthTest(kDefaultRequestParameters, expectation, kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
EXPECT_TRUE(test_auto_reauthn_permission_delegate_->embargoed_origins_.count(
OriginFromString(kRpUrl)));
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
/*expected_succeeded=*/true,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that auto re-authn with a single account where the account is a
// returning user sets the sign-in mode to auto.
TEST_F(RequestServiceTest, AutoReauthnForSingleReturningUserSingleAccount) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
for (const auto& idp_info : kConfigurationValid.idp_info) {
ASSERT_EQ(idp_info.second.accounts.size(), 1u);
}
RequestExpectations expectation = kExpectationSuccess;
expectation.is_auto_selected = true;
RunAuthTest(kDefaultRequestParameters, expectation, kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
/*expected_succeeded=*/true,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to auto.
TEST_F(RequestServiceTest, AutoReauthnForSingleReturningUserMultipleAccounts) {
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdNicolas))
.WillRepeatedly(Return(std::nullopt));
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdPeter))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::nullopt));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
RequestExpectations expectation = kExpectationSuccess;
expectation.is_auto_selected = true;
RunAuthTest(kDefaultRequestParameters, expectation, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
/*expected_succeeded=*/true,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that auto re-authn with multiple accounts and multiple returning users
// sets the sign-in mode to explicit.
TEST_F(RequestServiceTest,
AutoReauthnForMultipleReturningUsersMultipleAccounts) {
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdNicolas))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdPeter))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::nullopt));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
std::vector<IdentityRequestAccountPtr> multiple_accounts = kMultipleAccounts;
multiple_accounts[0]->idp_claimed_login_state = LoginState::kSignIn;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = multiple_accounts;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
EXPECT_EQ(CountNumLoginStateIsSignin(), 2);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kMultiple,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that auto re-authn with single non-returning account sets the sign-in
// mode to explicit.
TEST_F(RequestServiceTest, AutoReauthnForZeroReturningUsers) {
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::nullopt));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
for (const auto& idp_info : kConfigurationValid.idp_info) {
ASSERT_EQ(idp_info.second.accounts.size(), 1u);
}
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignUp);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kZero,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to kExplicit if `mediation: required` is specified.
TEST_F(RequestServiceTest,
AutoReauthnForSingleReturningUserWithoutSettingAutoReauthn) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kRequired;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse,
MediationRequirement::kRequired);
}
// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to kExplicit if `RequiresUserMediation` is set
TEST_F(RequestServiceTest,
AutoReauthnForSingleReturningUserWithRequiresUserMediation) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend that auto re-authn is not disabled in settings.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
// Pretend that auto re-authn is not in embargo state.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
// Pretend that re-authn requires user mediation.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
.WillOnce(Return(true));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/true);
}
// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to kExplicit if "auto sign-in" is disabled.
TEST_F(RequestServiceTest,
AutoReauthnForSingleReturningUserWithAutoSigninDisabled) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend that auto re-authn is not in embargo state.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
// Pretend that re-authn does not require user mediation.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
.WillOnce(Return(false));
// Pretend that auto re-authn is disabled in settings.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(false));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
}
// Test that if browser has not observed sign-in in the past, the sign-in mode
// is set to explicit regardless the account's login state.
TEST_F(RequestServiceTest, AutoReauthnBrowserNotObservedSigninBefore) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::nullopt));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
// Set IDP claims user is signed in.
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull]
.accounts[0]
->idp_claimed_login_state = LoginState::kSignIn;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
}
// Test that if browser has not observed sign-in in the past, but the IdP has
// third-party cookies access, the sign-in mode is set to auto if IdP claims
// that the user is returning.
TEST_F(RequestServiceTest,
AutoReauthnBrowserNotObservedSigninButIdpHasThirdPartyCookiesAccess) {
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::nullopt));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
// Pretend the IdP was given third-party cookies access.
EXPECT_CALL(*test_api_permission_delegate_,
HasThirdPartyCookiesAccess(_, GURL(kProviderUrlFull),
OriginFromString(kRpUrl)))
.WillRepeatedly(Return(true));
// Sharing permission won't be granted with this setup.
EXPECT_CALL(*test_permission_delegate_,
GrantSharingPermission(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), std::string(kAccountId)))
.Times(0);
// Set IDP claims user is signed in.
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull]
.accounts[0]
->idp_claimed_login_state = LoginState::kSignIn;
RequestExpectations expectation = kExpectationSuccess;
expectation.is_auto_selected = true;
RunAuthTest(kDefaultRequestParameters, expectation, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
}
// Test that auto re-authn for a first time user sets the sign-in mode to
// explicit.
TEST_F(RequestServiceTest, AutoReauthnForFirstTimeUser) {
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignUp);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
}
// Test that auto re-authn where the auto re-authn permission is blocked sets
// the sign-in mode to explicit.
TEST_F(RequestServiceTest, AutoReauthnWithBlockedAutoReauthnPermissions) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the auto re-authn permission has been blocked for this account.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(false));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/true,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that auto re-authn where the auto re-authn cooldown is on sets
// the sign-in mode to explicit.
TEST_F(RequestServiceTest, AutoReauthnWithCooldown) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the auto re-authn permission has been granted for this account.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
// Pretend that auto re-authn is embargoed.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(true));
RequestExpectations expectations = kExpectationSuccess;
expectations.standalone_console_message =
"Auto re-authn was previously triggered less than 10 minutes ago. Only "
"one auto re-authn request can be made every 10 minutes.";
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/true,
/*expected_prevent_silent_access=*/false);
}
// Test that no network request is sent if `mediation: silent` is used and user
// has not granted sharing permission in the past.
TEST_F(RequestServiceTest,
AutoReauthnMediationSilentFailWithNoSharingPermission) {
// Pretend the sharing permission has not been granted for any account.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(false));
// Pretend the auto re-authn is disabled in settings.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/
"Silent mediation issue: the user has not used FedCM on this "
"site with this identity provider.",
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
EXPECT_FALSE(DidFetchAnyEndpoint());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
MediationRequirement::kSilent);
ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test that no network request is sent if `mediation: silent` is used and auto
// re-authn is in cooldown.
TEST_F(RequestServiceTest, AutoReauthnMediationSilentFailWithEmbargo) {
// Pretend the sharing permission has been granted for some account.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(true));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/
"Silent mediation issue: auto re-authn is in quiet period "
"because "
"it was recently used on this site.",
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
EXPECT_FALSE(DidFetchAnyEndpoint());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
MediationRequirement::kSilent);
ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/true,
/*expected_prevent_silent_access=*/false);
}
// Test that no network request is sent if `mediation: silent` is used and user
// mediation is required, e.g. `preventSilentAccess` has been invoked
TEST_F(RequestServiceTest,
AutoReauthnMediationSilentFailWithRequiresUserMediation) {
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
.WillOnce(Return(true));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/
"Silent mediation issue: preventSilentAccess() has been invoked "
"on the site.",
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
EXPECT_FALSE(DidFetchAnyEndpoint());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
MediationRequirement::kSilent);
ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/true);
}
// Test that no network request is sent if `mediation: silent` is used and user
// has disabled "auto sign-in".
TEST_F(RequestServiceTest,
AutoReauthnMediationSilentFailWithPasswordManagerAutoSigninDisabled) {
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(false));
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
.WillOnce(Return(false));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/
"Silent mediation issue: the user has disabled auto re-authn.",
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
EXPECT_FALSE(DidFetchAnyEndpoint());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
MediationRequirement::kSilent);
ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/true,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test `mediation: silent` could fail silently after fetching accounts
TEST_F(RequestServiceTest,
AutoReauthnMediationSilentFailWithTwoReturningAccounts) {
// Pretend the sharing permission has been granted for some account.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdNicolas))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdPeter))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the sharing permission has not been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::nullopt));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillRepeatedly(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillRepeatedly(Return(false));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/
"Silent mediation issue: the user has used FedCM with multiple "
"accounts on this site.",
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
std::vector<IdentityRequestAccountPtr> multiple_accounts = kMultipleAccounts;
multiple_accounts[0]->idp_claimed_login_state = LoginState::kSignIn;
multiple_accounts[1]->idp_claimed_login_state = LoginState::kSignIn;
configuration.idp_info[kProviderUrlFull].accounts = multiple_accounts;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
MediationRequirement::kSilent);
ExpectAutoReauthnMetrics(Metrics::NumAccounts::kMultiple,
/*expected_succeeded=*/false,
/*expected_auto_reauthn_setting_blocked=*/false,
/*expected_auto_reauthn_embargoed=*/false,
/*expected_prevent_silent_access=*/false);
}
// Test `mediation: silent` fails silently after a failed accounts fetch.
TEST_F(RequestServiceTest,
AutoReauthnMediationSilentFailNotShowMismatchAfterAccountsFetch) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
// Pretend the sharing permission has been granted for exactly one account for
// the first IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
// Ensure auto reauthn is not considered as disabled.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillRepeatedly(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillRepeatedly(Return(false));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
// Let the first IDP accounts fetch fail.
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
IdpNetworkRequestManager::ParseStatus::kNoResponseError;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that `mediation: required` sets the sign-in mode to explicit even though
// other auto re-authn conditions are met.
TEST_F(RequestServiceTest, AutoReauthnMediationRequired) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::nullopt));
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull]
.accounts[0]
->idp_claimed_login_state = LoginState::kSignIn;
configuration.mediation_requirement = MediationRequirement::kRequired;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
LoginState::kSignIn);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse,
MediationRequirement::kRequired);
}
TEST_F(RequestServiceTest, MetricsForSuccessfulSignInCase) {
// Pretends that the sharing permission has been granted for this account.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(_, _, OriginFromString(kProviderUrlFull),
kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_EQ(LoginState::kSignIn,
all_accounts_for_display()[0]->browser_trusted_login_state);
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.IsSignInUser", 1, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AccountFieldsType",
static_cast<int>(AccountFieldsType::kNameOrEmailAndOtherIdentifier), 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 1, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
1, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpMode",
static_cast<int>(RpMode::kPassive), 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.VerifyingDialogResult",
static_cast<int>(VerifyingDialogResult::kSuccessExplicit), 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.FrameType", static_cast<int>(RequesterFrameType::kMainFrame),
1);
ExpectUKMPresence("Timing.ShowAccountsDialog");
ExpectUKMPresenceInternal(
"Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
FedCmEntry::kEntryName);
ExpectUKMPresenceInternal("Timing.ShowAccountsDialogBreakdown.AccountsFetch",
FedCmEntry::kEntryName);
ExpectUKMPresenceInternal(
"Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch",
FedCmEntry::kEntryName);
ExpectUKMPresence("Timing.ContinueOnDialog");
ExpectUKMPresence("Timing.IdTokenResponse");
ExpectUKMPresence("Timing.TurnaroundTime");
ExpectNoUKMPresence("Timing.CancelOnDialog");
ExpectUKMPresence("Timing.AccountsDialogShownDuration");
ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
ExpectUkmValue("RpMode", static_cast<int>(RpMode::kPassive));
ExpectUkmValue("VerifyingDialogResult",
static_cast<int>(VerifyingDialogResult::kSuccessExplicit));
ExpectUkmValue("FrameType", static_cast<int>(RequesterFrameType::kMainFrame));
ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}
// Test that request fails if account picker is explicitly dismissed.
TEST_F(RequestServiceTest, MetricsForUIExplicitlyDismissed) {
base::HistogramTester histogram_tester_;
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
for (const auto& idp_info : kConfigurationValid.idp_info) {
ASSERT_EQ(idp_info.second.accounts.size(), 1u);
}
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kClose;
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
ASSERT_TRUE(did_show_accounts_dialog());
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignUp);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
ExpectUKMPresence("Timing.ShowAccountsDialog");
ExpectUKMPresence("Timing.CancelOnDialog");
ExpectNoUKMPresence("Timing.ContinueOnDialog");
ExpectNoUKMPresence("Timing.IdTokenResponse");
ExpectNoUKMPresence("Timing.TurnaroundTime");
ExpectUKMPresence("Timing.AccountsDialogShownDuration");
ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
ExpectStatusMetrics(TokenStatus::kShouldEmbargo);
}
// Test that request is not completed if user ignores the UI.
TEST_F(RequestServiceTest, UIIsIgnored) {
base::HistogramTester histogram_tester_;
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
task_environment()->FastForwardBy(base::Minutes(10));
EXPECT_FALSE(auth_helper_->was_callback_called());
// The dialog should have been shown. The dialog controller should not be
// destroyed.
ASSERT_TRUE(did_show_accounts_dialog());
EXPECT_TRUE(weak_dialog_controller);
// Only the time to show the account dialog gets recorded.
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Status.RequestIdToken", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
}
TEST_F(RequestServiceTest, MetricsForWebContentsVisible) {
base::HistogramTester histogram_tester;
// Sets RenderFrameHost to visible
test_rvh()->SimulateWasShown();
ASSERT_EQ(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
content::PageVisibilityState::kVisible);
// Pretends that the sharing permission has been granted for this account.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(_, _, OriginFromString(kProviderUrlFull),
kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_EQ(LoginState::kSignIn,
all_accounts_for_display()[0]->browser_trusted_login_state);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsVisible", 1, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 1, 1);
}
// Test that request could succeed with auto re-authn even if the web contents
// invisible.
TEST_F(RequestServiceTest, MetricsForWebContentsInvisible) {
base::HistogramTester histogram_tester;
test_rvh()->SimulateWasShown();
ASSERT_EQ(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
content::PageVisibilityState::kVisible);
// Sets the RenderFrameHost to invisible
test_rvh()->SimulateWasHidden();
ASSERT_NE(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
content::PageVisibilityState::kVisible);
// Pretends that the sharing permission has been granted for this account.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(_, _, OriginFromString(kProviderUrlFull),
kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_EQ(LoginState::kSignIn,
all_accounts_for_display()[0]->browser_trusted_login_state);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsVisible", 0, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 1, 1);
}
TEST_F(RequestServiceTest, MetricsForFeatureIsDisabled) {
test_api_permission_delegate_->permission_override_ =
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_VARIATIONS);
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kDisabledInFlags,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
ExpectStatusMetrics(TokenStatus::kDisabledInFlags);
}
TEST_F(RequestServiceTest,
MetricsForFeatureIsDisabledNotDoubleCountedWithUnhandledRequest) {
test_api_permission_delegate_->permission_override_ =
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_VARIATIONS);
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
// Delete the request before DelayTimer kicks in.
federated_auth_request_impl_->ResetAndDeleteThis();
// If double counted, these samples would not be unique so the following
// checks will fail.
ExpectStatusMetrics(TokenStatus::kDisabledInFlags);
}
TEST_F(RequestServiceTest,
MetricsForFeatureIsDisabledNotDoubleCountedWithAbortedRequest) {
test_api_permission_delegate_->permission_override_ =
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_VARIATIONS);
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
// Abort the request before DelayTimer kicks in.
federated_auth_request_impl_->CancelTokenRequest();
// If double counted, these samples would not be unique so the following
// checks will fail.
ExpectStatusMetrics(TokenStatus::kDisabledInFlags);
}
// Test that sign-in states match if IDP claims that user is signed in and
// browser also observes that user is signed in.
TEST_F(RequestServiceTest, MetricsForSignedInOnBothIdpAndBrowser) {
// Set browser observes user is signed in.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
// Set IDP claims user is signed in.
MockConfiguration configuration = kConfigurationValid;
std::vector<IdentityRequestAccountPtr> all_accounts_for_display =
std::vector<IdentityRequestAccountPtr>(kSingleAccount.begin(),
kSingleAccount.end());
all_accounts_for_display[0]->idp_claimed_login_state = LoginState::kSignIn;
configuration.idp_info[kProviderUrlFull].accounts = all_accounts_for_display;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.SignInStateMatch",
SignInStateMatchStatus::kMatch, 1);
ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus::kMatch);
}
// Test that sign-in states match if IDP claims that user is not signed in and
// browser also observes that user is not signed in.
TEST_F(RequestServiceTest, MetricsForNotSignedInOnBothIdpAndBrowser) {
// Set browser observes user is not signed in.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::nullopt));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
// By default, IDP claims user is not signed in.
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.SignInStateMatch",
SignInStateMatchStatus::kMatch, 1);
ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus::kMatch);
}
// Test that sign-in states mismatch if IDP claims that user is signed in but
// browser observes that user is not signed in.
TEST_F(RequestServiceTest, MetricsForOnlyIdpClaimedSignIn) {
// Set browser observes user is not signed in.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::nullopt));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
// Set IDP claims user is signed in.
MockConfiguration configuration = kConfigurationValid;
std::vector<IdentityRequestAccountPtr> all_accounts_for_display =
std::vector<IdentityRequestAccountPtr>(kSingleAccount.begin(),
kSingleAccount.end());
all_accounts_for_display[0]->idp_claimed_login_state = LoginState::kSignIn;
configuration.idp_info[kProviderUrlFull].accounts = all_accounts_for_display;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Status.SignInStateMatch",
SignInStateMatchStatus::kIdpClaimedSignIn, 1);
ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus::kIdpClaimedSignIn);
}
// Test that sign-in states mismatch if IDP claims that user is not signed in
// but browser observes that user is signed in.
TEST_F(RequestServiceTest, MetricsForOnlyBrowserObservedSignIn) {
// Set browser observes user is signed in.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Status.SignInStateMatch",
SignInStateMatchStatus::kBrowserObservedSignIn, 1);
ExpectSignInStateMatchStatusUKM(
SignInStateMatchStatus::kBrowserObservedSignIn);
}
// Test that embargo is requested if the
// IdentityRequestDialogController::ShowAccountsDialog() callback requests it.
TEST_F(RequestServiceTest, RequestEmbargo) {
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kClose;
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.count(
main_test_rfh()->GetLastCommittedOrigin()));
}
// Test that the embargo dismiss count is reset when the user grants consent via
// the FedCM dialog.
TEST_F(RequestServiceTest, RemoveEmbargoOnUserConsent) {
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.empty());
}
// Test that token request fails if FEDERATED_IDENTITY_API content setting is
// disabled for the RP origin.
TEST_F(RequestServiceTest, ApiBlockedForOrigin) {
test_api_permission_delegate_->permission_override_ =
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_SETTINGS);
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kDisabledInSettings,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
ExpectStatusMetrics(TokenStatus::kDisabledInSettings);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", false, 1);
ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, false);
}
// Test that token request succeeds if FEDERATED_IDENTITY_API content setting is
// enabled for RP origin but disabled for an unrelated origin.
TEST_F(RequestServiceTest, ApiBlockedForUnrelatedOrigin) {
const url::Origin kUnrelatedOrigin = OriginFromString("https://rp2.example/");
test_api_permission_delegate_->permission_override_ =
std::make_pair(kUnrelatedOrigin, ApiPermissionStatus::BLOCKED_SETTINGS);
ASSERT_NE(main_test_rfh()->GetLastCommittedOrigin(), kUnrelatedOrigin);
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
class RequestServiceTestCancelConsistency
: public RequestServiceTest,
public ::testing::WithParamInterface<int> {};
INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
RequestServiceTestCancelConsistency,
::testing::Values(false, true),
::testing::PrintToStringParamName());
// Test that the RP cannot use CancelTokenRequest() to determine whether
// Option 1: FedCM dialog is shown but user has not interacted with it
// Option 2: FedCM API is disabled via variations
TEST_P(RequestServiceTestCancelConsistency, AccountNotSelected) {
const bool fedcm_disabled = GetParam();
if (fedcm_disabled) {
test_api_permission_delegate_->permission_override_ =
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_VARIATIONS);
}
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
request_remote_->CancelTokenRequest();
WaitForCurrentAuthRequest();
FederatedAuthRequestResult result =
fedcm_disabled ? FederatedAuthRequestResult::kDisabledInFlags
: FederatedAuthRequestResult::kCanceled;
RequestExpectations expectations{RequestTokenStatus::kErrorCanceled, result,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
CheckAuthExpectations(configuration, expectations);
}
namespace {
// TestDialogController which disables FedCM API when FedCM account selection
// dialog is shown.
class DisableApiWhenDialogShownDialogController : public TestDialogController {
public:
DisableApiWhenDialogShownDialogController(
MockConfiguration configuration,
TestApiPermissionDelegate* api_permission_delegate,
const url::Origin rp_origin_to_disable)
: TestDialogController(configuration),
api_permission_delegate_(api_permission_delegate),
rp_origin_to_disable_(rp_origin_to_disable) {}
~DisableApiWhenDialogShownDialogController() override = default;
DisableApiWhenDialogShownDialogController(
const DisableApiWhenDialogShownDialogController&) = delete;
DisableApiWhenDialogShownDialogController& operator=(
DisableApiWhenDialogShownDialogController&) = delete;
bool ShowAccountsDialog(
content::RelyingPartyData rp_data,
const std::vector<IdentityProviderDataPtr>& idp_list,
const std::vector<IdentityRequestAccountPtr>& accounts,
blink::mojom::RpMode rp_mode,
const std::vector<IdentityRequestAccountPtr>& new_accounts,
IdentityRequestDialogController::AccountSelectionCallback on_selected,
IdentityRequestDialogController::LoginToIdPCallback on_add_account,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::AccountsDisplayedCallback
accounts_displayed_callback) override {
// Disable FedCM API
api_permission_delegate_->permission_override_ = std::make_pair(
rp_origin_to_disable_, ApiPermissionStatus::BLOCKED_SETTINGS);
// Call parent class method in order to store callback parameters.
return TestDialogController::ShowAccountsDialog(
std::move(rp_data), idp_list, accounts, rp_mode, new_accounts,
std::move(on_selected), std::move(on_add_account),
std::move(dismiss_callback), std::move(accounts_displayed_callback));
}
private:
raw_ptr<TestApiPermissionDelegate> api_permission_delegate_;
url::Origin rp_origin_to_disable_;
};
} // namespace
// Test that the request fails if user proceeds with the sign in workflow after
// disabling the API while an existing accounts dialog is shown.
TEST_F(RequestServiceTest, ApiDisabledAfterAccountsDialogShown) {
base::HistogramTester histogram_tester_;
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kDisabledInSettings,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
url::Origin rp_origin_to_disable = main_test_rfh()->GetLastCommittedOrigin();
SetDialogController(
std::make_unique<DisableApiWhenDialogShownDialogController>(
kConfigurationValid, test_api_permission_delegate_.get(),
rp_origin_to_disable));
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));
ukm_loop.Run();
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
ExpectUKMPresence("Timing.ShowAccountsDialog");
ExpectNoUKMPresence("Timing.ContinueOnDialog");
ExpectNoUKMPresence("Timing.IdTokenResponse");
ExpectNoUKMPresence("Timing.TurnaroundTime");
ExpectUKMPresence("Timing.AccountsDialogShownDuration");
ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
ExpectStatusMetrics(TokenStatus::kDisabledInSettings);
}
// Test the disclosure_text_shown value in the token post data for sign-up case.
TEST_F(RequestServiceTest, DisclosureTextShownForFirstTimeUser) {
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) + "&disclosure_text_shown=true" +
"&is_auto_selected=false&mode=passive&fields=name,email,picture&"
"disclosure_shown_for="
"name,email,picture");
SetNetworkRequestManager(std::move(checker));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
// Test the disclosure_text_shown value in the token post data for returning
// user case.
TEST_F(RequestServiceTest, DisclosureTextNotShownForReturningUser) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) +
"&disclosure_text_shown=false&is_auto_"
"selected=false&mode=passive&fields=name,email,picture");
SetNetworkRequestManager(std::move(checker));
MockConfiguration config = kConfigurationValid;
config.mediation_requirement = MediationRequirement::kRequired;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, config);
}
// Test that the values in the token post data are escaped according to the
// application/x-www-form-urlencoded spec.
TEST_F(RequestServiceTest, TokenEndpointPostDataEscaping) {
const std::string kAccountIdWithSpace("account id");
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts[0]->id =
kAccountIdWithSpace;
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=account+id&disclosure_text_"
"shown=true&is_auto_selected=false&mode=passive&fields=name,email,"
"picture&disclosure_"
"shown_for=name,email,picture");
SetNetworkRequestManager(std::move(checker));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}
// Test that the is_auto_selected value in the token post
// data for sign-up case.
TEST_F(RequestServiceTest, AutoSelectedFlagForNewUser) {
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) + "&disclosure_text_shown=true" +
"&is_auto_selected=false&mode=passive&fields=name,email,picture&"
"disclosure_shown_for="
"name,email,picture");
SetNetworkRequestManager(std::move(checker));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
// Test that the is_auto_selected value in the token post
// data for returning user with `mediation:required`.
TEST_F(RequestServiceTest,
AutoSelectedFlagForReturningUserWithMediationRequired) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) +
"&disclosure_text_shown=false" +
"&is_auto_selected=false&mode=passive&fields=name,email,picture");
SetNetworkRequestManager(std::move(checker));
MockConfiguration config = kConfigurationValid;
config.mediation_requirement = MediationRequirement::kRequired;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, config);
}
// Test that the is_auto_selected value in the token post
// data for returning user with `mediation:optional`.
TEST_F(RequestServiceTest,
AutoSelectedFlagForReturningUserWithMediationOptional) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) +
"&disclosure_text_shown=false" +
"&is_auto_selected=true&mode=passive&fields=name,email,picture");
SetNetworkRequestManager(std::move(checker));
MockConfiguration config = kConfigurationValid;
config.mediation_requirement = MediationRequirement::kOptional;
RequestExpectations expectation = kExpectationSuccess;
expectation.is_auto_selected = true;
RunAuthTest(kDefaultRequestParameters, expectation, config);
}
// Test that the is_auto_selected value in the token post
// data for the quiet period use case.
TEST_F(RequestServiceTest, AutoSelectedFlagIfInQuietPeriod) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
// Pretend the auto re-authn is in quiet period.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(true));
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) +
"&disclosure_text_shown=false&is_auto_"
"selected=false&mode=passive&fields=name,email,picture");
SetNetworkRequestManager(std::move(checker));
RequestExpectations expectations = kExpectationSuccess;
expectations.standalone_console_message =
"Auto re-authn was previously triggered less than 10 minutes ago. Only "
"one auto re-authn request can be made every 10 minutes.";
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
}
TEST_F(RequestServiceTest, RegisteredIdpInIdAssertion) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmIdPRegistration);
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
RequestParameters request = kDefaultRequestParameters;
constexpr char kType[] = "someType";
request.identity_providers[0].type = kType;
checker->SetExpectedTokenPostData(
"client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
"&account_id=" + std::string(kAccountId) + "&disclosure_text_shown=true" +
"&is_auto_selected=false&mode=passive&fields=name,email,picture&"
"disclosure_shown_for="
"name,email,picture"
"&type=" +
kType);
SetNetworkRequestManager(std::move(checker));
MockConfiguration config = kConfigurationValid;
config.idp_info[kProviderUrlFull].config.types = {kType};
RunAuthTest(request, kExpectationSuccess, config);
}
namespace {
// TestIdpNetworkRequestManager subclass which runs the `account_list_task`
// passed-in to the constructor prior to the accounts endpoint returning.
class IdpNetworkRequestManagerClientMetadataTaskRunner
: public TestIdpNetworkRequestManager {
public:
explicit IdpNetworkRequestManagerClientMetadataTaskRunner(
base::OnceClosure client_metadata_task)
: client_metadata_task_(std::move(client_metadata_task)) {}
IdpNetworkRequestManagerClientMetadataTaskRunner(
const IdpNetworkRequestManagerClientMetadataTaskRunner&) = delete;
IdpNetworkRequestManagerClientMetadataTaskRunner& operator=(
const IdpNetworkRequestManagerClientMetadataTaskRunner&) = delete;
void FetchClientMetadata(const GURL& client_metadata_endpoint_url,
const std::string& client_id,
int rp_brand_icon_ideal_size,
int rp_brand_icon_minimum_size,
FetchClientMetadataCallback callback) override {
// Make copies because running the task might destroy
// RequestService and invalidate the references.
GURL client_metadata_endpoint_url_copy = client_metadata_endpoint_url;
std::string client_id_copy = client_id;
if (client_metadata_task_) {
std::move(client_metadata_task_).Run();
}
TestIdpNetworkRequestManager::FetchClientMetadata(
client_metadata_endpoint_url_copy, client_id_copy,
rp_brand_icon_ideal_size, rp_brand_icon_minimum_size,
std::move(callback));
}
private:
base::OnceClosure client_metadata_task_;
};
void NavigateToUrl(content::WebContents* web_contents, const GURL& url) {
static_cast<TestWebContents*>(web_contents)
->NavigateAndCommit(url, ui::PAGE_TRANSITION_LINK);
}
} // namespace
// Test that the account chooser is not shown if the page navigates prior to the
// client metadata endpoint request completing and BFCache is enabled.
TEST_F(RequestServiceTest, NavigateDuringClientMetadataFetchBFCacheEnabled) {
base::test::ScopedFeatureList list;
list.InitWithFeaturesAndParameters(
GetBasicBackForwardCacheFeatureForTesting(),
GetDefaultDisabledBackForwardCacheFeaturesForTesting());
ASSERT_TRUE(IsBackForwardCacheEnabled());
SetNetworkRequestManager(
std::make_unique<IdpNetworkRequestManagerClientMetadataTaskRunner>(
base::BindOnce(&NavigateToUrl, web_contents(), GURL(kRpOtherUrl))));
RequestExpectations expectations = {
RequestTokenStatus::kError,
// No console message is received, so pass
// FederatedAuthRequestResult::kSuccess.
FederatedAuthRequestResult::kSuccess,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 0, 1);
}
// Test that the account chooser is not shown if the page navigates prior to the
// accounts endpoint request completing and BFCache is disabled.
TEST_F(RequestServiceTest, NavigateDuringClientMetadataFetchBFCacheDisabled) {
base::test::ScopedFeatureList list;
list.InitAndDisableFeature(features::kBackForwardCache);
ASSERT_FALSE(IsBackForwardCacheEnabled());
SetNetworkRequestManager(
std::make_unique<IdpNetworkRequestManagerClientMetadataTaskRunner>(
base::BindOnce(&NavigateToUrl, web_contents(), GURL(kRpOtherUrl))));
RequestExpectations expectations = {
/*return_status=*/std::nullopt,
// When the RenderFrameHost changes on navigation, no console message is
// received, so pass FederatedAuthRequestResult::kSuccess.
main_rfh()->ShouldChangeRenderFrameHostOnSameSiteNavigation()
? FederatedAuthRequestResult::kSuccess
: FederatedAuthRequestResult::kError,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
}
// Test that the accounts are reordered so that accounts with a LoginState equal
// to kSignIn are listed before accounts with a LoginState equal to kSignUp.
TEST_F(RequestServiceTest, ReorderMultipleAccounts) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
// Check the account order using the account ids.
ASSERT_EQ(all_accounts_for_display().size(), 3u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdNicolas);
EXPECT_EQ(all_accounts_for_display()[2]->id, kAccountIdZach);
}
// Test that first API call with a given IDP is not affected by the
// IdpSigninStatus bit.
TEST_F(RequestServiceTest, IdpSigninStatusTestFirstTimeFetchSuccess) {
EXPECT_CALL(*test_permission_delegate_,
SetIdpSigninStatus(OriginFromString(kProviderUrlFull), true, _))
.Times(1);
std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
std::make_unique<IdpNetworkRequestManagerParamChecker>();
checker->SetExpectations(kClientId, kAccountId);
SetNetworkRequestManager(std::move(checker));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
// Test that first API call with a given IDP will not show a UI in case of
// failure during fetching accounts.
TEST_F(RequestServiceTest, IdpSigninStatusTestFirstTimeFetchNoFailureUi) {
EXPECT_CALL(*test_permission_delegate_,
SetIdpSigninStatus(OriginFromString(kProviderUrlFull), false, _))
.Times(1);
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that a failure UI will be displayed if the accounts fetch is failed but
// the IdpSigninStatus claims that the user is signed in.
TEST_F(RequestServiceTest, IdpSigninStatusTestShowFailureUi) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
configuration.idp_signin_status_mismatch_dialog_action =
IdpSigninStatusMismatchDialogAction::kClose;
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that API calls will fail before sending any network request if
// IdpSigninStatus shows that the user is not signed in with the IDP. No failure
// UI is displayed.
TEST_F(RequestServiceTest,
IdpSigninStatusTestApiFailedIfUserNotSignedInWithIdp) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kNotSignedInWithIdp,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
namespace {
// TestIdpNetworkRequestManager which enables specifying the ParseStatus for
// config and accounts endpoint fetch.
class ParseStatusOverrideIdpNetworkRequestManager
: public TestIdpNetworkRequestManager {
public:
ParseStatus config_parse_status_{ParseStatus::kSuccess};
ParseStatus accounts_parse_status_{ParseStatus::kSuccess};
ParseStatusOverrideIdpNetworkRequestManager() = default;
~ParseStatusOverrideIdpNetworkRequestManager() override = default;
ParseStatusOverrideIdpNetworkRequestManager(
const ParseStatusOverrideIdpNetworkRequestManager&) = delete;
ParseStatusOverrideIdpNetworkRequestManager& operator=(
const ParseStatusOverrideIdpNetworkRequestManager&) = delete;
void FetchConfig(const GURL& provider,
blink::mojom::RpMode rp_mode,
int idp_brand_icon_ideal_size,
int idp_brand_icon_minimum_size,
FetchConfigCallback callback) override {
if (config_parse_status_ != ParseStatus::kSuccess) {
++num_fetched_[FetchedEndpoint::CONFIG];
FetchStatus fetch_status{config_parse_status_, net::HTTP_OK};
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), fetch_status,
IdpNetworkRequestManager::Endpoints(),
IdentityProviderMetadata()));
return;
}
TestIdpNetworkRequestManager::FetchConfig(
provider, rp_mode, idp_brand_icon_ideal_size,
idp_brand_icon_minimum_size, std::move(callback));
}
void SendAccountsRequest(const url::Origin& idp_origin,
const GURL& accounts_url,
const std::string& client_id,
AccountsRequestCallback callback) override {
if (accounts_parse_status_ != ParseStatus::kSuccess) {
++num_fetched_[FetchedEndpoint::ACCOUNTS];
FetchStatus fetch_status{accounts_parse_status_, net::HTTP_OK};
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), fetch_status,
std::vector<IdentityRequestAccountPtr>()));
return;
}
TestIdpNetworkRequestManager::SendAccountsRequest(
idp_origin, accounts_url, client_id, std::move(callback));
}
};
} // namespace
// Test behavior for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) User signs-in
// 3) User selects "Continue" in account chooser dialog.
TEST_F(RequestServiceTest, FailureUiThenSuccessfulSignin) {
SetNetworkRequestManager(
std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
auto* network_manager =
static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
test_network_request_manager_.get());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
// Setup IdP sign-in status mismatch.
network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_FALSE(did_show_accounts_dialog());
// Simulate user signing into IdP by updating the IdP sign-in status and
// calling the observer.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
network_manager->accounts_parse_status_ = ParseStatus::kSuccess;
federated_auth_request_impl_->OnIdpSigninStatusReceived(
kIdpOrigin, /*idp_signin_status=*/true);
WaitForCurrentAuthRequest();
CheckAuthExpectations(kConfigurationValid, kExpectationSuccess);
EXPECT_TRUE(did_show_accounts_dialog());
// After the IdP sign-in status was updated, the endpoints should have been
// fetched a 2nd time.
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectUKMPresence("AccountsDialogShown2");
ExpectUKMPresence("MismatchDialogShown2");
ExpectUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}
// Test behavior for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) User switches tabs
// 3) User signs into IdP in different tab
TEST_F(RequestServiceTest, FailureUiThenSuccessfulSigninButHidden) {
SetNetworkRequestManager(
std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
auto* network_manager =
static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
test_network_request_manager_.get());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
// Setup IdP sign-in status mismatch.
network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_FALSE(did_show_accounts_dialog());
// Simulate the user switching to a different tab.
test_rvh()->SimulateWasHidden();
// Simulate user signing into IdP by updating the IdP signin status and
// calling observer.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
network_manager->accounts_parse_status_ = ParseStatus::kSuccess;
federated_auth_request_impl_->OnIdpSigninStatusReceived(
kIdpOrigin, /*idp_signin_status=*/true);
WaitForCurrentAuthRequest();
CheckAuthExpectations(kConfigurationValid, kExpectationSuccess);
// The FedCM dialog should switch to the account picker. The user should
// see a new dialog when they switch back to the FedCM tab.
EXPECT_TRUE(did_show_accounts_dialog());
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectUKMPresence("AccountsDialogShown2");
ExpectUKMPresence("MismatchDialogShown2");
ExpectUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}
// Test behavior for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) In a different tab, user signs into different IdP
TEST_F(RequestServiceTest, FailureUiSigninFromDifferentIdp) {
SetNetworkRequestManager(
std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
auto* network_manager =
static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
test_network_request_manager_.get());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
url::Origin kOtherOrigin = OriginFromString("https://idp.other");
// Setup IdP sign-in status mismatch.
network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
// Close mismatch dialog upon sign-in status change to check that the
// appropriate metrics are recorded.
MockConfiguration configuration = kConfigurationValid;
configuration.idp_signin_status_mismatch_dialog_action =
IdpSigninStatusMismatchDialogAction::kClose;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_FALSE(did_show_accounts_dialog());
size_t num_well_known_fetches = NumFetched(FetchedEndpoint::WELL_KNOWN);
// Simulate user signing into different IdP by updating the IdP signin status
// and calling observer.
test_permission_delegate_->idp_signin_statuses_[kOtherOrigin] = true;
federated_auth_request_impl_->OnIdpSigninStatusReceived(
kOtherOrigin, /*idp_signin_status=*/true);
base::RunLoop().RunUntilIdle();
// No fetches should have been triggered.
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), num_well_known_fetches);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectNoUKMPresence("AccountsDialogShown2");
ExpectUKMPresence("MismatchDialogShown2");
ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}
// Test that for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) IdP sign-in status is updated
// 3) Accounts endpoint still returns an empty list
// That ShowFailureDialog() is called a 2nd time after the IdP sign-in status
// update.
TEST_F(RequestServiceTest, FailureUiAccountEndpointKeepsFailing) {
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
MockConfiguration configuration = kConfigurationValid;
// Setup IdP sign-in status mismatch.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_FALSE(did_show_accounts_dialog());
// Update IdP sign-in status. Keep accounts endpoint returning empty list.
// Close mismatch dialog upon sign-in status change to check that the
// appropriate metrics are recorded.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
weak_dialog_controller->SetIdpSigninStatusMismatchDialogAction(
IdpSigninStatusMismatchDialogAction::kClose);
federated_auth_request_impl_->OnIdpSigninStatusReceived(
kIdpOrigin, /*idp_signin_status=*/true);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2u, dialog_controller_state_
.num_show_idp_signin_status_mismatch_dialog_requests);
// After the IdP sign-in status was updated, the endpoints should have been
// fetched a 2nd time.
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectNoUKMPresence("AccountsDialogShown2");
ExpectUKMPresence("MismatchDialogShown2");
ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}
// Test that for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) IdP sign-in status is updated
// 3) A different endpoint fails during the fetch initiated by the IdP sign-in
// status update.
// That user is shown IdP-sign-in-failure dialog.
TEST_F(RequestServiceTest, FailureUiThenFailDifferentEndpoint) {
SetNetworkRequestManager(
std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
auto* network_manager =
static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
test_network_request_manager_.get());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
// Setup IdP sign-in status mismatch.
network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
// Make the fetch triggered by the IdP sign-in status changing fail for a
// different endpoint.
network_manager->config_parse_status_ = ParseStatus::kInvalidResponseError;
// Simulate user signing into IdP by updating the IdP signin status and
// calling the observer.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
network_manager->accounts_parse_status_ = ParseStatus::kSuccess;
federated_auth_request_impl_->OnIdpSigninStatusReceived(
kIdpOrigin, /*idp_signin_status=*/true);
WaitForCurrentAuthRequest();
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
CheckAuthExpectations(kConfigurationValid, expectations);
// The user should be shown IdP-sign-in-failure dialog.
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_EQ(1u, dialog_controller_state_
.num_show_idp_signin_status_mismatch_dialog_requests);
// After the IdP sign-in status was updated, the endpoints should have been
// fetched a 2nd time.
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectNoUKMPresence("AccountsDialogShown2");
ExpectUKMPresence("MismatchDialogShown2");
ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}
// Test that when IdpSigninStatus API does not have any state for an IDP, that
// the state transitions to sign-in if the accounts endpoint returns a
// non-empty list of accounts.
TEST_F(
RequestServiceTest,
IdpSigninStatusMetricsModeUndefinedTransitionsToSignedinWhenHaveAccounts) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = std::nullopt;
EXPECT_CALL(*test_permission_delegate_,
SetIdpSigninStatus(OriginFromString(kProviderUrlFull), true, _));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
TEST_F(RequestServiceTest, AllSuccessfulMultiIdpRequestWithoutIdpReorder) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
// Set the account from the first IDP as returning as well, so the first
// selected account should be that one (second IDP also has returning accounts
// and no reordering should happen).
MockConfiguration config = kConfigurationMultiIdpValid;
config.idp_info[kProviderUrlFull].accounts[0]->idp_claimed_login_state =
LoginState::kSignIn;
RunAuthTest(kDefaultMultiIdpRequestParameters, kExpectationSuccess, config);
EXPECT_EQ(2u, NumFetched(FetchedEndpoint::ACCOUNTS));
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 1);
ExpectUKMPresence("Timing.ContinueOnDialog");
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 1);
ExpectUKMPresence("Timing.IdTokenResponse");
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 1);
ExpectUKMPresence("Timing.TurnaroundTime");
ExpectUkmValueInEntry("AccountsRequestSent2", FedCmEntry::kEntryName, 2);
ExpectUkmValueInEntry("AccountsRequestSent2", FedCmIdpEntry::kEntryName, 1,
2);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 2);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
ExpectUKMCount("Timing.AccountsDialogShownDuration", FedCmEntry::kEntryName,
1);
ExpectUKMCount("Timing.AccountsDialogShownDuration",
FedCmIdpEntry::kEntryName, 2);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
ExpectNoUKMPresence("Timing.MismatchDialogShown2");
ExpectUKMCount("Timing.ShowAccountsDialog", FedCmEntry::kEntryName, 1);
ExpectUKMCount("Timing.ShowAccountsDialog", FedCmIdpEntry::kEntryName, 2);
ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
ExpectUkmValue("NumIdpsRequested", 2, 2);
ExpectUkmValue("NumIdpsMismatch", 0, 2);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
1);
ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}
// Test successful multi IDP FedCM request.
TEST_F(RequestServiceTest, AllSuccessfulMultiIdpRequestWithIdpReorder) {
RequestExpectations expectations = kExpectationSuccess;
// Since the first IDP does not set the login state of the account but the
// second IDP has one with state set to SignIn, selecting the first account
// means that the second IDP is the one that is selected.
expectations.selected_idp_config_url = kProviderTwoUrlFull;
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations,
kConfigurationMultiIdpValid);
EXPECT_EQ(2u, NumFetched(FetchedEndpoint::ACCOUNTS));
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
ExpectUKMCount("Timing.ShowAccountsDialog", FedCmEntry::kEntryName, 1);
ExpectUKMCount("Timing.ShowAccountsDialog", FedCmIdpEntry::kEntryName, 2);
ExpectUkmValueInEntry(
"Status.RequestIdToken", FedCmEntry::kEntryName,
static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse));
ExpectUkmValueInEntry(
"Status.RequestIdToken", FedCmIdpEntry::kEntryName,
static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse),
/*expected_count=*/2,
/*other_values_allowed=*/true);
ExpectUkmValueInEntry("Status.RequestIdToken", FedCmIdpEntry::kEntryName,
static_cast<int>(TokenStatus::kOtherIdpChosen),
/*expected_count=*/2,
/*other_values_allowed=*/true);
ExpectUkmValue("NumIdpsRequested", 2, 2);
ExpectUkmValue("NumIdpsMismatch", 0, 2);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
1);
ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}
// Test fetching information for the 1st IdP failing, and succeeding for the
// second.
TEST_F(RequestServiceTest, FirstIdpWellKnownInvalid) {
// Intentionally fail the 1st provider's request by having an invalid
// well-known file.
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.idp_info[kProviderUrlFull].well_known.provider_urls =
std::set<std::string>{"https://not-in-list.example"};
RequestExpectations expectations = {
RequestTokenStatus::kSuccess,
FederatedAuthRequestResult::kConfigNotInWellKnown,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/kProviderTwoUrlFull};
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::CONFIG), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
EXPECT_EQ(NumFetched(FetchedEndpoint::TOKEN), 1u);
ExpectUKMCount("AccountsRequestSent2", FedCmEntry::kEntryName, 1);
ExpectUKMCount("AccountsRequestSent2", FedCmIdpEntry::kEntryName, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
1);
ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}
// Test fetching information for the 1st IdP succeeding, and failing for the
// second.
TEST_F(RequestServiceTest, SecondIdpWellKnownInvalid) {
// Intentionally fail the 2nd provider's request by having an invalid
// well-known file.
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.idp_info[kProviderTwoUrlFull].well_known.provider_urls =
std::set<std::string>{"https://not-in-list.example"};
RequestExpectations expectations = {
RequestTokenStatus::kSuccess,
FederatedAuthRequestResult::kConfigNotInWellKnown,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/kProviderUrlFull};
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::CONFIG), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
EXPECT_EQ(NumFetched(FetchedEndpoint::TOKEN), 1u);
ExpectUKMCount("AccountsRequestSent2", FedCmEntry::kEntryName, 1);
ExpectUKMCount("AccountsRequestSent2", FedCmIdpEntry::kEntryName, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
1);
ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}
// Test fetching information for all of the IdPs failing.
TEST_F(RequestServiceTest, AllWellKnownsInvalid) {
// Intentionally fail the requests for both IdPs by returning an invalid
// well-known file.
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.idp_info[kProviderUrlFull].well_known.provider_urls =
std::set<std::string>{"https://not-in-list.example"};
configuration.idp_info[kProviderTwoUrlFull].well_known.provider_urls =
std::set<std::string>{"https://not-in-list.example"};
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigNotInWellKnown,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
EXPECT_EQ(NumFetched(FetchedEndpoint::CONFIG), 2u);
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
ExpectNoUKMPresence("Timing.ShowAccountsDialog");
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 0);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
1);
ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}
// Test multi IDP FedCM request with duplicate IDPs should throw an error.
TEST_F(RequestServiceTest, DuplicateIdpMultiIdpRequest) {
RequestParameters request_parameters = kDefaultMultiIdpRequestParameters;
request_parameters.identity_providers =
std::vector<IdentityProviderParameters>{
request_parameters.identity_providers[0],
request_parameters.identity_providers[0]};
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kError,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(request_parameters, expectations, kConfigurationMultiIdpValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectTotalCount("Blink.FedCm.IdentityProvidersCount", 0);
ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 0);
}
// Test that API can succeed with multiple IdPs, if one IdP is signed out but
// the other isn't.
TEST_F(RequestServiceTest, MultiIdpWithOneIdpSignedOut) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;
RequestExpectations expectations = kExpectationSuccess;
expectations.selected_idp_config_url = kProviderTwoUrlFull;
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations,
kConfigurationMultiIdpValid);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that API shows all accounts if the user logs in to the IDP with the
// mismatch UI.
TEST_F(RequestServiceTest, MultiIdpLoginToOneIdp) {
url::Origin providerOrigin = OriginFromString(kProviderUrlFull);
test_permission_delegate_->idp_signin_statuses_[providerOrigin] = true;
MockConfiguration config = kConfigurationMultiIdpValid;
// Second IDP has invalid accounts response.
config.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
config.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultMultiIdpRequestParameters, config);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
EXPECT_TRUE(did_show_accounts_dialog());
// The second IDP has 3 accounts, so those should be showing up.
EXPECT_EQ(all_accounts_for_display().size(), 3u);
EXPECT_FALSE(federated_auth_request_impl_->HasUserTriedToSignInToIdp(
GURL(kProviderUrlFull)));
EXPECT_FALSE(federated_auth_request_impl_->HasUserTriedToSignInToIdp(
GURL(kProviderTwoUrlFull)));
// First, simulate the user clicking on the sign in to IDP active.
SimulateLoginToIdP();
// Then, simulate user signing into IdP by updating the IdP signin status and
// calling the observer.
test_permission_delegate_->idp_signin_statuses_[providerOrigin] = true;
// Second IDP will now correctly return its account.
config.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kSuccess;
SetConfig(config);
federated_auth_request_impl_->OnIdpSigninStatusReceived(
providerOrigin, /*idp_signin_status=*/true);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 3u);
EXPECT_TRUE(did_show_accounts_dialog());
// The first IDP only has a single account, and the total of accounts is
// now 4.
EXPECT_EQ(all_accounts_for_display().size(), 4u);
EXPECT_EQ(new_accounts().size(), 1u);
EXPECT_TRUE(federated_auth_request_impl_->HasUserTriedToSignInToIdp(
GURL(kProviderUrlFull)));
EXPECT_FALSE(federated_auth_request_impl_->HasUserTriedToSignInToIdp(
GURL(kProviderTwoUrlFull)));
}
// Test that API can succeed with multiple IdPs, if all IDPs have login status
// mismatch.
TEST_F(RequestServiceTest, MultiIdpWithAllIdpsMismatch) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = true;
// Set the config so that both accounts fetches result in failure.
MockConfiguration config = kConfigurationMultiIdpValid;
config.idp_info[kProviderUrlFull].accounts_response.parse_status =
IdpNetworkRequestManager::ParseStatus::kEmptyListError;
config.idp_info[kProviderTwoUrlFull].accounts_response.parse_status =
IdpNetworkRequestManager::ParseStatus::kInvalidResponseError;
// Need to change the accounts dialog action since we won't get any accounts.
config.accounts_dialog_action = AccountsDialogAction::kClose;
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, config);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
EXPECT_TRUE(all_accounts_for_display().empty());
auto mismatch_idps = displayed_mismatch_idps();
ASSERT_EQ(mismatch_idps.size(), 2u);
EXPECT_EQ(mismatch_idps[0], "idp.example");
EXPECT_EQ(mismatch_idps[1], "idp2.example");
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMCount("Timing.MismatchDialogShownDuration", FedCmEntry::kEntryName,
1);
ExpectUKMCount("Timing.MismatchDialogShownDuration",
FedCmIdpEntry::kEntryName, 2);
ExpectUkmValue("Status.RequestIdToken",
static_cast<int>(TokenStatus::kShouldEmbargo), 2);
ExpectUkmValue("NumIdpsRequested", 2, 2);
ExpectUkmValue("NumIdpsMismatch", 2, 2);
}
TEST_F(RequestServiceTest, MultiIdpWithOneIdpMismatch) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = true;
// Set the config so that both accounts fetches result in failure.
MockConfiguration config = kConfigurationMultiIdpValid;
config.idp_info[kProviderTwoUrlFull].accounts_response.parse_status =
IdpNetworkRequestManager::ParseStatus::kEmptyListError;
RunAuthTest(kDefaultMultiIdpRequestParameters, kExpectationSuccess, config);
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
EXPECT_FALSE(all_accounts_for_display().empty());
auto mismatch_idps = displayed_mismatch_idps();
ASSERT_EQ(mismatch_idps.size(), 1u);
EXPECT_EQ(mismatch_idps[0], "idp2.example");
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
ExpectUKMCount("Timing.AccountsDialogShownDuration", FedCmEntry::kEntryName,
1);
ExpectUKMCount("Timing.AccountsDialogShownDuration",
FedCmIdpEntry::kEntryName, 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
ExpectUKMCount("Timing.MismatchDialogShownDuration", FedCmEntry::kEntryName,
0);
ExpectUKMCount("Timing.MismatchDialogShownDuration",
FedCmIdpEntry::kEntryName, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
1);
ExpectUKMCount("Timing.ShowAccountsDialog", FedCmEntry::kEntryName, 1);
ExpectUKMCount("Timing.ShowAccountsDialog", FedCmIdpEntry::kEntryName, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsDialogShown", 1);
ExpectUKMCount("AccountsDialogShown2", FedCmEntry::kEntryName, 1);
ExpectUKMCount("AccountsDialogShown2", FedCmIdpEntry::kEntryName, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.MismatchDialogShown2",
0);
ExpectUKMCount("MismatchDialogShown2", FedCmEntry::kEntryName, 0);
ExpectUKMCount("MismatchDialogShown2", FedCmIdpEntry::kEntryName, 1);
ExpectUkmValueInEntry(
"Status.RequestIdToken", FedCmEntry::kEntryName,
static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse));
ExpectUkmValueInEntry(
"Status.RequestIdToken", FedCmIdpEntry::kEntryName,
static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse),
/*expected_count=*/2,
/*other_values_allowed=*/true);
ExpectUkmValueInEntry("Status.RequestIdToken", FedCmIdpEntry::kEntryName,
static_cast<int>(TokenStatus::kOtherIdpChosen),
/*expected_count=*/2,
/*other_values_allowed=*/true);
ExpectUkmValue("NumIdpsRequested", 2, 2);
ExpectUkmValue("NumIdpsMismatch", 1, 2);
}
// Test that API can succeed with multiple IdPs, if silent mediation is used but
// only one IdP has a returning account.
TEST_F(RequestServiceTest,
MultiIdpWithSilentMediationAndReturningAccountInSecondIdp) {
// Pretend the sharing permission has not been granted for any account for the
// first IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(false));
// Pretend the sharing permission has been granted for exactly one account for
// the second IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull)))
.WillOnce(Return(true));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdPeter))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdNicolas))
.WillRepeatedly(Return(std::nullopt));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::nullopt));
// Ensure auto reauthn is not considered as disabled.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.Times(3)
.WillRepeatedly(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.Times(3)
.WillRepeatedly(Return(false));
RequestExpectations expectations = kExpectationSuccess;
expectations.selected_idp_config_url = kProviderTwoUrlFull;
expectations.is_auto_selected = true;
// There will be a console message due to using mediation:silent and having an
// IdP that does not have a returning account.
expectations.standalone_console_message =
"Silent mediation issue: the user has not used FedCM on this site with "
"this identity provider.";
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
RunAuthDontWaitForCallback(kDefaultMultiIdpRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that API fails with multiple IdPs, if silent mediation is used and two
// IdPs have a single returning account.
TEST_F(RequestServiceTest,
MultiIdpWithSilentMediationAndReturningAccountInTwoIdps) {
// Pretend the sharing permission has been granted for exactly one account for
// the first IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
// Pretend the sharing permission has been granted for exactly one account for
// the second IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull)))
.WillOnce(Return(true));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdPeter))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdNicolas))
.WillRepeatedly(Return(std::nullopt));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::nullopt));
// Ensure auto reauthn is not considered as disabled.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillRepeatedly(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillRepeatedly(Return(false));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSilentMediationFailure,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
RunAuthDontWaitForCallback(kDefaultMultiIdpRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
// Accounts still need to be fetched since there could have been a single
// returning account.
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that when there are two IDPs with sharing permissions but the account
// fetch fails for one of them, mediation silent can still succeed.
TEST_F(RequestServiceTest, MultiIdpWithSilentMediationAndOneIdpFetchFailure) {
// Mark both IDPs as logged in.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = true;
// Pretend the sharing permission has been granted for exactly one account for
// the first IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull)))
.WillOnce(Return(true));
// Pretend the sharing permission has been granted for exactly one account for
// the second IdP.
EXPECT_CALL(
*test_permission_delegate_,
HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull)))
.WillOnce(Return(true));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdPeter))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::nullopt));
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderTwoUrlFull), kAccountIdNicolas))
.WillRepeatedly(Return(std::nullopt));
// Ensure auto reauthn is not considered as disabled.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.Times(3)
.WillRepeatedly(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.Times(3)
.WillRepeatedly(Return(false));
RequestExpectations expectations = kExpectationSuccess;
expectations.selected_idp_config_url = kProviderTwoUrlFull;
expectations.is_auto_selected = true;
expectations.standalone_console_message =
"Silent mediation was requested, but the conditions to achieve it were "
"not met.";
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.mediation_requirement = MediationRequirement::kSilent;
// Let the first IDP accounts fetch fail.
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
IdpNetworkRequestManager::ParseStatus::kNoResponseError;
RunAuthDontWaitForCallback(kDefaultMultiIdpRequestParameters, configuration);
EXPECT_FALSE(auth_helper_->was_callback_called());
WaitForCurrentAuthRequest();
CheckAuthExpectations(configuration, expectations);
// Accounts still need to be fetched since there could have been a single
// returning account.
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
TEST_F(RequestServiceTest, MultiIdpLoggedOut) {
// Mark both IDPs as logged out so the request fails early.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = false;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kNotSignedInWithIdp,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
request_remote_.set_disconnect_handler(auth_helper_->quit_closure());
RunAuthDontWaitForCallback(kDefaultMultiIdpRequestParameters,
kConfigurationMultiIdpValid);
base::RunLoop().RunUntilIdle();
// The callback must be delayed.
EXPECT_FALSE(auth_helper_->was_callback_called());
WaitForCurrentAuthRequest();
CheckAuthExpectations(kConfigurationMultiIdpValid, expectations);
}
TEST_F(RequestServiceTest, MultiIdpWithError) {
MockConfiguration configuration = kConfigurationMultiIdpValid;
ErrorDialogType error_dialog_type =
ErrorDialogType::kGenericNonEmptyWithoutUrl;
TokenResponseType token_response_type =
TokenResponseType::kTokenReceivedAndErrorReceivedAndContinueOnNotReceived;
configuration.token_response.parse_status =
ParseStatus::kInvalidResponseError;
configuration.error_dialog_type = error_dialog_type;
configuration.token_response_type = token_response_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
error_dialog_type, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Error.ErrorDialogResult",
ErrorDialogResult::kCloseWithoutMoreDetails, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
token_response_type, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Error.ErrorUrlType", 0);
ExpectUKMPresence("Error.ErrorDialogType");
ExpectUKMPresence("Error.ErrorDialogResult");
ExpectUKMPresence("Error.TokenResponseType");
ExpectNoUKMPresence("Error.ErrorUrlType");
}
TEST_F(RequestServiceTest, TooManyRequests) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
// Reset the network request manager so we can check that we fetch no
// endpoints in the subsequent call.
configuration.accounts_dialog_action =
AccountsDialogAction::kSelectFirstAccount;
SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
// The next FedCM request should fail since the initial request has not yet
// been finalized.
RequestExpectations expectations = {
RequestTokenStatus::kErrorTooManyRequests,
FederatedAuthRequestResult::kTooManyRequests,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_FALSE(DidFetchAnyEndpoint());
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
// Only count the first request, the second request that resulted in
// RequestTokenStatus::kErrorTooManyRequests should not be counted.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 0,
1);
ExpectUkmValue("MultipleRequestsRpMode",
static_cast<int>(MultipleRequestsRpMode::kPassiveThenPassive));
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.MultipleRequestsFromDifferentIdPs", 0, 1);
ExpectUkmValueInEntry("MultipleRequestsFromDifferentIdPs",
FedCmEntry::kEntryName, false);
// Check for RP-keyed UKM presence.
ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
// Metrics for each request should be recorded separately with different
// session IDs.
ExpectTwoUniqueSessionIDs();
}
TEST_F(RequestServiceTest, TooManyRequestsDifferentIdP) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
// Reset the network request manager so we can check that we fetch no
// endpoints in the subsequent call.
configuration.accounts_dialog_action =
AccountsDialogAction::kSelectFirstAccount;
SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
// The next FedCM request should fail since the initial request has not yet
// been finalized.
RequestExpectations expectations = {
RequestTokenStatus::kErrorTooManyRequests,
FederatedAuthRequestResult::kTooManyRequests,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
// Initiates a new API call with a different IdP.
RequestParameters request{kDefaultRequestParameters};
request.identity_providers[0].provider = kProviderTwoUrlFull;
RunAuthTest(request, expectations, configuration);
EXPECT_FALSE(DidFetchAnyEndpoint());
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.MultipleRequestsFromDifferentIdPs", 1, 1);
ExpectUkmValueInEntry("MultipleRequestsFromDifferentIdPs",
FedCmEntry::kEntryName, true);
}
TEST_F(RequestServiceTest, ActiveModeTooManyRequestsWithNewPassiveFlow) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RunAuthDontWaitForCallback(parameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
// Reset the network request manager so we can check that we fetch no
// endpoints in the subsequent call.
configuration.accounts_dialog_action =
AccountsDialogAction::kSelectFirstAccount;
SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
// The next FedCM request should fail since a passive flow cannot replace
// another active flow.
RequestExpectations expectations = {
RequestTokenStatus::kErrorTooManyRequests,
FederatedAuthRequestResult::kTooManyRequests,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_FALSE(DidFetchAnyEndpoint());
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
// Only count the first request, the second request that resulted in
// RequestTokenStatus::kErrorTooManyRequests should not be counted.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 2,
1);
ExpectUkmValue("MultipleRequestsRpMode",
static_cast<int>(MultipleRequestsRpMode::kActiveThenPassive));
// Check for RP-keyed UKM presence.
ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
}
TEST_F(RequestServiceTest, ActiveModeTooManyRequestsWithNewActiveFlow) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RunAuthDontWaitForCallback(parameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
// Reset the network request manager so we can check that we fetch no
// endpoints in the subsequent call.
configuration.accounts_dialog_action =
AccountsDialogAction::kSelectFirstAccount;
SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
// The next FedCM request should fail since a active flow cannot replace
// another active flow.
RequestExpectations expectations = {
RequestTokenStatus::kErrorTooManyRequests,
FederatedAuthRequestResult::kTooManyRequests,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RunAuthTest(parameters, expectations, configuration);
EXPECT_FALSE(DidFetchAnyEndpoint());
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
// Only count the first request, the second request that resulted in
// RequestTokenStatus::kErrorTooManyRequests should not be counted.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 3,
1);
ExpectUkmValue("MultipleRequestsRpMode",
static_cast<int>(MultipleRequestsRpMode::kActiveThenActive));
// Check for RP-keyed UKM presence.
ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
}
TEST_F(RequestServiceTest, PassiveReplacedByActiveFlow) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
RequestExpectations active_flow_expectations = kExpectationSuccess;
active_flow_expectations.standalone_console_message =
"The request is replaced by a new one with active mode.";
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
// Create new test helpers so that we can send the second request.
SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
std::unique_ptr<AuthRequestCallbackHelper> active_flow_auth_helper =
std::make_unique<AuthRequestCallbackHelper>();
RunAuthTest(parameters, active_flow_expectations, kConfigurationValid,
active_flow_auth_helper.get());
RequestExpectations passive_flow_expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kReplacedByActiveMode,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
CheckAuthExpectations(configuration, passive_flow_expectations);
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
// Count both the replaced passive request and the active request.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 2,
1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 1,
1);
ExpectUkmValue("MultipleRequestsRpMode",
static_cast<std::underlying_type_t<MultipleRequestsRpMode>>(
MultipleRequestsRpMode::kPassiveThenActive));
// Check for RP-keyed UKM presence.
ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
// Metrics for each request should be recorded separately with different
// session IDs.
ExpectTwoUniqueSessionIDs();
}
// TestIdpNetworkRequestManager subclass which records requests to metrics
// endpoint.
class IdpNetworkRequestMetricsRecorder : public TestIdpNetworkRequestManager {
public:
IdpNetworkRequestMetricsRecorder() = default;
IdpNetworkRequestMetricsRecorder(const IdpNetworkRequestMetricsRecorder&) =
delete;
IdpNetworkRequestMetricsRecorder& operator=(
const IdpNetworkRequestMetricsRecorder&) = delete;
void SendSuccessfulTokenRequestMetrics(
const GURL& metrics_endpoint_url,
base::TimeDelta api_call_to_show_dialog_time,
base::TimeDelta show_dialog_to_continue_clicked_time,
base::TimeDelta account_selected_to_token_response_time,
base::TimeDelta api_call_to_token_response_time) override {
metrics_endpoints_notified_success_.push_back(metrics_endpoint_url);
}
void SendFailedTokenRequestMetrics(
const GURL& metrics_endpoint_url,
bool did_show_ui,
MetricsEndpointErrorCode error_code) override {
metrics_endpoints_notified_failure_.push_back(metrics_endpoint_url);
}
const std::vector<GURL>& get_metrics_endpoints_notified_success() {
return metrics_endpoints_notified_success_;
}
const std::vector<GURL>& get_metrics_endpoints_notified_failure() {
return metrics_endpoints_notified_failure_;
}
private:
std::vector<GURL> metrics_endpoints_notified_success_;
std::vector<GURL> metrics_endpoints_notified_failure_;
};
// Test that the metrics endpoint is not notified when the FedCM API is in
// cooldown.
TEST_F(RequestServiceTest, MetricsEndpointDuringCooldown) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);
test_api_permission_delegate_->RecordDismissAndEmbargo(
OriginFromString(kRpUrl));
std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
std::make_unique<IdpNetworkRequestMetricsRecorder>();
IdpNetworkRequestMetricsRecorder* metrics_recorder =
unique_metrics_recorder.get();
SetNetworkRequestManager(std::move(unique_metrics_recorder));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kDisabledInSettings,
/*standalone_console_message=*/std::nullopt,
/* selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_FALSE(DidFetchAnyEndpoint());
EXPECT_TRUE(
metrics_recorder->get_metrics_endpoints_notified_success().empty());
EXPECT_TRUE(
metrics_recorder->get_metrics_endpoints_notified_failure().empty());
}
// Test that the metrics endpoint is notified as a result of a successful
// multi-IDP RequestService::RequestToken() call.
TEST_F(RequestServiceTest, MetricsEndpointMultiIdp) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);
std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
std::make_unique<IdpNetworkRequestMetricsRecorder>();
IdpNetworkRequestMetricsRecorder* metrics_recorder =
unique_metrics_recorder.get();
SetNetworkRequestManager(std::move(unique_metrics_recorder));
// Since the first IDP does not set the login state of the account but the
// second IDP has one with state set to SignIn, selecting the first account
// means that the second IDP is the one that is selected.
RequestExpectations expectations = kExpectationSuccess;
expectations.selected_idp_config_url = kProviderTwoUrlFull;
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations,
kConfigurationMultiIdpValid);
EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_success(),
ElementsAre("https://idp2.example/metrics"));
EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_failure(),
ElementsAre(kMetricsEndpoint));
}
// Test that the metrics endpoint is notified when
// RequestService::RequestToken() call fails.
TEST_F(RequestServiceTest, MetricsEndpointMultiIdpFail) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);
std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
std::make_unique<IdpNetworkRequestMetricsRecorder>();
IdpNetworkRequestMetricsRecorder* metrics_recorder =
unique_metrics_recorder.get();
SetNetworkRequestManager(std::move(unique_metrics_recorder));
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/* selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationMultiIdpValid;
configuration.accounts_dialog_action = AccountsDialogAction::kClose;
RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_TRUE(
metrics_recorder->get_metrics_endpoints_notified_success().empty());
EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_failure(),
ElementsAre(kMetricsEndpoint, "https://idp2.example/metrics"));
}
TEST_F(RequestServiceTest, AccountsSortedWithTimestamps) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
// First account is kSignUp so the fact that it has a last used timestamp
// should not affect its relative ordering.
EXPECT_CALL(*test_permission_delegate_,
GetLastUsedTimestamp(
OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdNicolas))
.WillRepeatedly(Return(std::make_optional<base::Time>(
base::Time() + base::Microseconds(10))));
// The second account is marked signed in but has no last used timestamp.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdPeter))
.WillRepeatedly(Return(std::nullopt));
// The third account is marked sign (as is the second), but since it has a
// timestamp it should show first.
configuration.idp_info[kProviderUrlFull]
.accounts[2]
->idp_claimed_login_state = LoginState::kSignIn;
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountIdZach))
.WillRepeatedly(Return(std::make_optional<base::Time>(
base::Time() + base::Microseconds(1))));
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
// Account order should be: accounts[2], accounts[1], accounts[0].
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdZach);
EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdPeter);
EXPECT_EQ(all_accounts_for_display()[2]->id, kAccountIdNicolas);
}
TEST_F(RequestServiceTest, AccountLabelMultipleAccountsNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.requested_label =
"invalid_label";
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AccountLabel.NumMatchingAccounts",
Metrics::NumAccounts::kZero, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
ExpectUkmValueInEntry("AccountLabel.NumMatchingAccounts",
FedCmEntry::kEntryName, 0);
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, AccountLabelMultipleAccountsOneMatch) {
RequestParameters parameters = kDefaultRequestParameters;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.requested_label = "label";
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.AccountLabel.NumMatchingAccounts",
Metrics::NumAccounts::kOne, 1);
ExpectUkmValueInEntry("AccountLabel.NumMatchingAccounts",
FedCmEntry::kEntryName, 1);
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, LoginHintSingleAccountIdMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = kAccountId;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kSingleAccountWithHint;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, LoginHintSingleAccountEmailMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = kEmail;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kSingleAccountWithHint;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->display_identifier, kEmail);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, LoginHintSingleAccountNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = "incorrect_login_hint";
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kSingleAccountWithHint;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
ExpectNoUKMPresence("HasSigninAccount");
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
1);
ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
0);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, LoginHintFirstAccountMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = kAccountIdNicolas;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdNicolas);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, LoginHintLastAccountMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = kAccountIdZach;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdZach);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
1, 1);
ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, LoginHintMultipleAccountsNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = "incorrect_login_hint";
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
1);
ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
0);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
}
TEST_F(RequestServiceTest, DomainHintSingleAccountMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kDomainHint;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kSingleAccountWithDomainHint;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, DomainHintSingleAccountStarMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint =
RequestService::kWildcardDomainHint;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kSingleAccountWithDomainHint;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, DomainHintSingleAccountStarNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint =
RequestService::kWildcardDomainHint;
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 0);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, DomainHintSingleAccountNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = "incorrect_domain_hint";
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kSingleAccountWithDomainHint;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 0);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 1, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
}
TEST_F(RequestServiceTest, DomainHintNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kDomainHint;
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(parameters, expectations, kConfigurationValid);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 0);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, DomainHintMultipleAccountsSingleMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kOtherDomainHint;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdZach);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 1);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
1, 1);
}
TEST_F(RequestServiceTest, DomainHintMultipleAccountsMultipleMatches) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kDomainHint;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 2u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdNicolas);
EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdZach);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts",
Metrics::NumAccounts::kMultiple, 1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 2);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
2, 1);
}
TEST_F(RequestServiceTest, DomainHintMultipleAccountsStar) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint =
RequestService::kWildcardDomainHint;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 2u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdNicolas);
EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdZach);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts",
Metrics::NumAccounts::kMultiple, 1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 2);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, DomainHintMultipleAccountsNoMatch) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = "incorrect_domain_hint";
const RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsListEmpty,
{kFilterNoMatchMessage},
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts =
kMultipleAccountsWithHintsAndDomains;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
1);
ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
FedCmEntry::kEntryName, 0);
ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}
TEST_F(RequestServiceTest, PictureFetch) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts[0]->picture =
GURL(kAccountPicture);
// This ensures we don't fetch client metadata, to test a different codepath.
configuration.idp_info[kProviderUrlFull]
.accounts[0]
->idp_claimed_login_state = LoginState::kSignIn;
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);
EXPECT_FALSE(all_accounts_for_display()[0]->decoded_picture.IsEmpty());
EXPECT_EQ(kAccountPictureSize,
all_accounts_for_display()[0]->decoded_picture.Width());
EXPECT_EQ(kAccountPictureSize,
all_accounts_for_display()[0]->decoded_picture.Height());
}
TEST_F(RequestServiceTest, PictureFetchMultipleAccounts) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
configuration.idp_info[kProviderUrlFull].accounts[0]->picture =
GURL(kAccountPicture);
configuration.idp_info[kProviderUrlFull].accounts[1]->picture =
GURL(kAccountPicture);
configuration.idp_info[kProviderUrlFull].accounts[2]->picture =
GURL(kAccountPicture404);
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
EXPECT_FALSE(all_accounts_for_display()[0]->decoded_picture.IsEmpty());
EXPECT_EQ(kAccountPictureSize,
all_accounts_for_display()[0]->decoded_picture.Width());
EXPECT_EQ(kAccountPictureSize,
all_accounts_for_display()[0]->decoded_picture.Height());
EXPECT_FALSE(all_accounts_for_display()[1]->decoded_picture.IsEmpty());
EXPECT_EQ(kAccountPictureSize,
all_accounts_for_display()[1]->decoded_picture.Width());
EXPECT_EQ(kAccountPictureSize,
all_accounts_for_display()[1]->decoded_picture.Height());
EXPECT_TRUE(all_accounts_for_display()[2]->decoded_picture.IsEmpty());
}
// Test that when FedCmRpContext flag is enabled and rp_context is specified,
// the FedCM request succeeds with the specified rp_context.
TEST_F(RequestServiceTest, RpContextIsSetToNonDefaultValue) {
RequestParameters request_parameters = kDefaultRequestParameters;
request_parameters.rp_context = blink::mojom::RpContext::kContinue;
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action =
AccountsDialogAction::kSelectFirstAccount;
RunAuthTest(request_parameters, kExpectationSuccess, configuration);
EXPECT_EQ(dialog_controller_state_.rp_context,
blink::mojom::RpContext::kContinue);
}
TEST_F(RequestServiceTest, WellKnownInvalidContentType) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull]
.well_known.fetch_status.parse_status =
ParseStatus::kInvalidContentTypeError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kWellKnownInvalidContentType,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
ukm_loop.Run();
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
ExpectStatusMetrics(TokenStatus::kWellKnownInvalidContentType);
}
TEST_F(RequestServiceTest, ConfigInvalidContentType) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.fetch_status.parse_status =
ParseStatus::kInvalidContentTypeError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigInvalidContentType,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
ukm_loop.Run();
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
ExpectStatusMetrics(TokenStatus::kConfigInvalidContentType);
}
TEST_F(RequestServiceTest, ClientMetadataInvalidContentType) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull]
.client_metadata.fetch_status.parse_status =
ParseStatus::kInvalidContentTypeError;
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
// The FedCM flow succeeds even if the client metadata fetch fails.
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ukm_loop.Run();
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}
TEST_F(RequestServiceTest, AccountsInvalidContentType) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidContentTypeError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kAccountsInvalidContentType,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
ukm_loop.Run();
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
ExpectStatusMetrics(TokenStatus::kAccountsInvalidContentType);
}
TEST_F(RequestServiceTest, IdTokenInvalidContentType) {
MockConfiguration configuration = kConfigurationValid;
configuration.token_response.parse_status =
ParseStatus::kInvalidContentTypeError;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenInvalidContentType,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
ukm_loop.Run();
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
ExpectStatusMetrics(TokenStatus::kIdTokenInvalidContentType);
}
// Test successful AuthZ request that returns tokens without opening
// pop-up windows.
TEST_F(RequestServiceTest, SuccessfulAuthZRequestNoPopUpWindow) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].fields = {"non_default_field"};
RunAuthTest(parameters, kExpectationSuccess, kConfigurationValid);
// When the authorization is delegated and the feature is enabled
// we don't fetch the client metadata endpoint (which is used to
// mediate - but not to delegate - the authorization prompt).
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
// Ensure that metrics were recorded.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpParametersAndScopeState",
RpParameters::kHasNonDefaultScope, 1);
}
// Test successful AuthZ request that request the opening of pop-up
// windows.
TEST_F(RequestServiceTest, SuccessfulAuthZRequestWithPopUpWindow) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].fields = {"non_default_field"};
MockConfiguration config = kConfigurationValid;
// Expect an access token to be produced, rather the typical idtoken.
config.token = "an-access-token";
// Set up the network expectations to return a "continue_on" response
// rather than the typical idtoken response.
GURL continue_on = GURL(kProviderUrlFull).Resolve("/more-permissions.php");
config.continue_on = std::move(continue_on);
std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
std::make_unique<IdpNetworkRequestMetricsRecorder>();
IdpNetworkRequestMetricsRecorder* metrics_recorder =
unique_metrics_recorder.get();
SetNetworkRequestManager(std::move(unique_metrics_recorder));
// Set up the UI dialog controller to show a pop-up window, rather
// than the typical mediated authorization prompt that generates
// an idtoken.
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
// When the pop-up window is opened, resolve it immediately by
// producing an access token.
std::unique_ptr<WebContents> modal(CreateTestWebContents());
auto impl = federated_auth_request_impl_;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &impl](const GURL& url) {
impl->OnResolve(GURL(kProviderUrlFull), std::nullopt,
"an-access-token");
return modal.get();
}));
RunAuthTest(parameters, kExpectationSuccess, config);
ExpectStatusMetrics(TokenStatus::kSuccessUsingIdentityProviderResolve);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.ContinueOn.PopupWindowStatus",
ContinueOnPopupStatus::kPopupOpened, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.ContinueOn.PopupWindowResult",
ContinueOnPopupResult::kTokenReceived, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOn.Response",
1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ContinueOn.TurnaroundTime", 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
// Authz popup should not be confused with use other account.
ExpectNoUKMPresence("UseOtherAccountResult");
EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_success(),
ElementsAre("https://idp.example/metrics"));
EXPECT_TRUE(
metrics_recorder->get_metrics_endpoints_notified_failure().empty());
// When the authorization is delegated and the feature is enabled
// we don't fetch the client metadata endpoint (which is used to
// mediate - but not to delegate - the authorization prompt).
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
}
// Test the continuation popup calling close().
TEST_F(RequestServiceTest, ContinuationPopupCallingClose) {
RequestParameters parameters = kDefaultRequestParameters;
MockConfiguration config = kConfigurationValid;
// Expect an access token to be produced, rather the typical idtoken.
config.token = "an-access-token";
// Set up the network expectations to return a "continue_on" response
// rather than the typical idtoken response.
GURL continue_on = GURL(kProviderUrlFull).Resolve("/more-permissions.php");
config.continue_on = std::move(continue_on);
// Set up the UI dialog controller to show a pop-up window, rather
// than the typical mediated authorization prompt that generates
// an idtoken.
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
// When the pop-up window is opened, resolve it immediately by
// producing an access token.
std::unique_ptr<WebContents> modal(CreateTestWebContents());
auto impl = federated_auth_request_impl_;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &impl](const GURL& url) {
impl->OnClose();
return modal.get();
}));
RequestExpectations error = {RequestTokenStatus::kError,
FederatedAuthRequestResult::kError,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(parameters, error, config);
ExpectStatusMetrics(
TokenStatus::kContinuationPopupClosedByIdentityProviderClose);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.ContinueOn.PopupWindowStatus",
ContinueOnPopupStatus::kPopupOpened, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.ContinueOn.PopupWindowResult",
ContinueOnPopupResult::kClosedByIdentityProviderClose, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOn.Response",
0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ContinueOn.TurnaroundTime", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
}
// Test successful AuthZ request that request the opening of pop-up
// windows.
TEST_F(RequestServiceTest, FailsLoadingAContinueOnForADifferentOrigin) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].fields = {"non_default_field"};
MockConfiguration config = kConfigurationValid;
// Set up the network expectations to return a "continue_on" response
// rather than the typical idtoken response.
GURL continue_on =
GURL("https://another-origin.example").Resolve("/more-permissions.php");
config.continue_on = std::move(continue_on);
RequestExpectations error = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenInvalidResponse,
// TODO(crbug.com/40262526): introduce a more granular error.
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(parameters, error, config);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.ContinueOn.PopupWindowStatus",
ContinueOnPopupStatus::kUrlNotSameOrigin, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.ContinueOn.PopupWindowResult",
ContinueOnPopupResult::kTokenReceived, 0);
// We only record timing on success.
histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOn.Response",
0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.ContinueOn.TurnaroundTime", 0);
}
// Test metrics for a request with parameters.
TEST_F(RequestServiceTest, RequestWithParameters) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].params_json = "{\"foo\", \"bar\"}";
RunAuthTest(parameters, kExpectationSuccess, kConfigurationValid);
// Ensure that metrics were recorded.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpParametersAndScopeState",
RpParameters::kHasParameters, 1);
}
// Test metrics for a request with parameters and scopes.
TEST_F(RequestServiceTest, RequestWithParametersAndScopes) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].fields = {"non_default_field"};
parameters.identity_providers[0].params_json = "{\"foo\", \"bar\"}";
RunAuthTest(parameters, kExpectationSuccess, kConfigurationValid);
// When the authorization is delegated and the feature is enabled
// we don't fetch the client metadata endpoint (which is used to
// mediate - but not to delegate - the authorization prompt).
EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
// Ensure that metrics were recorded.
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.RpParametersAndScopeState",
RpParameters::kHasParametersAndNonDefaultScope, 1);
}
// Test successfully signing-in users when they are signed-out on
// active flows.
TEST_F(RequestServiceTest, SignInWhenSignedOutOnActiveModeWithUserActivation) {
ExpectSuccessfulActiveFlow();
}
// Test active flow failure outside of user activation.
TEST_F(RequestServiceTest, ActiveFlowRequiresUserActivation) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
request_remote_.set_disconnect_handler(auth_helper_->quit_closure());
RequestExpectations error = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kMissingTransientUserActivation,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
// Active flow request gets rejected without delay.
RunAuthDontWaitForCallback(parameters, kConfigurationValid);
CheckAuthExpectations(kConfigurationValid, error);
EXPECT_FALSE(DidFetchAnyEndpoint());
}
// Test the active flow request fails without delay if IdP config is wrong.
TEST_F(RequestServiceTest, ActiveFlowWellKnownNotInList) {
RequestExpectations request_not_in_list = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kConfigNotInWellKnown,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
const char* idp_config_url =
kDefaultRequestParameters.identity_providers[0].provider;
const char* kWellKnownMismatchConfigUrl = "https://mismatch.example";
EXPECT_NE(std::string(idp_config_url), kWellKnownMismatchConfigUrl);
MockConfiguration config = kConfigurationValid;
config.idp_info[idp_config_url].well_known = {
{kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
// Active flow request gets rejected without delay.
RunAuthDontWaitForCallback(parameters, config);
CheckAuthExpectations(config, request_not_in_list);
EXPECT_TRUE(DidFetchWellKnownAndConfig());
EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}
TEST_F(RequestServiceTest, ActiveFlowWithUnknownLoginStatus) {
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
// Check that the LoginStatus is "unknown".
EXPECT_EQ(test_permission_delegate_->idp_signin_statuses_.count(kIdpOrigin),
0u);
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
// Check that the pop-up window is displayed.
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(Return(nullptr));
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RunAuthDontWaitForCallback(parameters, configuration);
}
// Test that active flow can skip the mismatch UI.
TEST_F(RequestServiceTest, ActiveFlowSkipsMismatchUI) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
// Check that the pop-up window is displayed.
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(Return(nullptr));
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RunAuthDontWaitForCallback(parameters, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}
// Test that active flow shows the loading dialog.
TEST_F(RequestServiceTest, ActiveFlowShowsLoadingUI) {
ExpectSuccessfulActiveFlow();
EXPECT_TRUE(dialog_controller_state_.did_show_loading_dialog);
}
// Test dismissing a active flow through the loading UI.
TEST_F(RequestServiceTest, ActiveFlowDismissLoadingUI) {
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kUiDismissedNoEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.loading_dialog_action = LoadingDialogAction::kClose;
RunAuthTest(parameters, expectations, configuration);
EXPECT_TRUE(dialog_controller_state_.did_show_loading_dialog);
}
TEST_F(RequestServiceTest, CloseModalDialogView) {
// Test that IdentityRegistry is notified when modal dialog view is closed.
EXPECT_FALSE(test_identity_registry_->notified_);
federated_auth_request_impl_->CloseModalDialogView();
EXPECT_TRUE(test_identity_registry_->notified_);
}
class RequestServiceNewTabTest : public RequestServiceTest {
protected:
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
InitConstants();
test_api_permission_delegate_ =
std::make_unique<TestApiPermissionDelegate>();
test_permission_delegate_ = std::make_unique<TestPermissionDelegate>();
test_auto_reauthn_permission_delegate_ =
std::make_unique<TestAutoReauthnPermissionDelegate>();
test_identity_registry_ = std::make_unique<TestIdentityRegistry>(
web_contents(), /*delegate=*/nullptr, GURL(kIdpUrl));
auth_helper_ = std::make_unique<AuthRequestCallbackHelper>();
static_cast<TestWebContents*>(web_contents())
->NavigateAndCommit(GURL("chrome://newtab/"), ui::PAGE_TRANSITION_LINK);
federated_auth_request_impl_ = &RequestService::CreateForTesting(
*main_test_rfh(), test_api_permission_delegate_.get(),
test_auto_reauthn_permission_delegate_.get(),
test_permission_delegate_.get(), test_identity_registry_.get(),
request_remote_.BindNewPipeAndPassReceiver());
std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
std::make_unique<TestIdpNetworkRequestManager>();
SetNetworkRequestManager(std::move(network_request_manager));
}
};
TEST_F(RequestServiceNewTabTest, SuccessfulFlow) {
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
}
class UserInfoCallbackHelper {
public:
UserInfoCallbackHelper() = default;
~UserInfoCallbackHelper() = default;
// This can only be called once per lifetime of this object.
blink::mojom::FederatedAuthRequest::RequestUserInfoCallback callback() {
return base::BindOnce(&UserInfoCallbackHelper::Complete,
base::Unretained(this));
}
// Returns when callback() is called, which can be immediately if it has
// already been called.
void WaitForCallback() {
if (was_called_) {
return;
}
wait_for_callback_loop_.Run();
}
void Complete(
blink::mojom::RequestUserInfoStatus user_info_status,
std::optional<std::vector<blink::mojom::IdentityUserInfoPtr>> user_info) {
CHECK(!was_called_);
was_called_ = true;
wait_for_callback_loop_.Quit();
}
private:
bool was_called_{false};
base::RunLoop wait_for_callback_loop_;
};
TEST_F(RequestServiceTest, RequestUserInfoFailure) {
blink::mojom::IdentityProviderConfigPtr config =
blink::mojom::IdentityProviderConfig::New();
config->config_url = GURL(kIdpUrl);
UserInfoCallbackHelper callback_helper;
// This request will fail right away (not from IDP origin).
federated_auth_request_impl_->RequestUserInfo(
std::move(config), base::BindOnce(&UserInfoCallbackHelper::Complete,
base::Unretained(&callback_helper)));
// This is a regression test and it passes if the test does not crash.
callback_helper.WaitForCallback();
}
// Tests that when an accounts dialog is shown, the appropriate metrics are
// recorded.
TEST_F(RequestServiceTest, AccountsDialogShownMetric) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ukm_loop.Run();
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsDialogShown", 1, 1);
ExpectUKMPresence("AccountsDialogShown2");
ExpectNoUKMPresence("MismatchDialogShown2");
}
// Tests that when a mismatch dialog is shown, the appropriate metrics are
// recorded.
TEST_F(RequestServiceTest, MismatchDialogShownMetric) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
MockConfiguration configuration = kConfigurationValid;
configuration.idp_signin_status_mismatch_dialog_action =
IdpSigninStatusMismatchDialogAction::kClose;
// Setup IdP sign-in status mismatch.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
ukm_loop.Run();
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
histogram_tester_.ExpectUniqueSample("Blink.FedCm.MismatchDialogShown", 1, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.MismatchDialogType",
Metrics::MismatchDialogType::kFirstWithoutHints, 1);
ExpectUKMPresence("MismatchDialogShown2");
ExpectNoUKMPresence("AccountsDialogShown2");
ExpectNoUKMPresence("HasSigninAccount");
}
// Tests that a mismatch dialog is shown twice.
TEST_F(RequestServiceTest, DoubleMismatchDialog) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
MockConfiguration configuration = kConfigurationValid;
// Setup IdP sign-in status mismatch.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kHttpNotFoundError;
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = "hint";
RunAuthDontWaitForCallback(parameters, configuration);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
federated_auth_request_impl_->OnIdpSigninStatusReceived(kIdpOrigin, true);
base::RunLoop().RunUntilIdle();
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
// The additional mismatch should be recorded in the metrics.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.MismatchDialogShown", 1, 2);
histogram_tester_.ExpectTotalCount("Blink.FedCm.MismatchDialogType", 2);
histogram_tester_.ExpectBucketCount(
"Blink.FedCm.MismatchDialogType",
Metrics::MismatchDialogType::kFirstWithHints, 1);
histogram_tester_.ExpectBucketCount(
"Blink.FedCm.MismatchDialogType",
Metrics::MismatchDialogType::kRepeatedWithHints, 1);
}
// Tests that when an accounts request is sent, the appropriate metrics are
// recorded.
TEST_F(RequestServiceTest, AccountsRequestSentMetric) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ukm_loop.Run();
EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsRequestSent", 1, 1);
ExpectUKMPresence("AccountsRequestSent2");
}
// Tests that when an accounts dialog is aborted, the appropriate duration
// metrics are recorded.
TEST_F(RequestServiceTest, AbortedAccountsDialogShownDurationMetric) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
// Abort the request.
federated_auth_request_impl_->CancelTokenRequest();
WaitForCurrentAuthRequest();
RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
FederatedAuthRequestResult::kCanceled,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
CheckAuthExpectations(configuration, expectations);
ukm_loop.Run();
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
ExpectUKMPresence("Timing.AccountsDialogShownDuration");
ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
}
// Tests that when a mismatch dialog is aborted, the appropriate duration
// metrics are recorded.
TEST_F(RequestServiceTest, AbortedMismatchDialogShownDurationMetric) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
SetNetworkRequestManager(
std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
auto* network_manager =
static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
test_network_request_manager_.get());
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
// Setup IdP sign-in status mismatch.
network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_FALSE(did_show_accounts_dialog());
// Abort the request.
federated_auth_request_impl_->CancelTokenRequest();
RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
FederatedAuthRequestResult::kCanceled,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
WaitForCurrentAuthRequest();
CheckAuthExpectations(kConfigurationValid, expectations);
ukm_loop.Run();
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}
// Tests that when requests are made to FedCM in succession, the appropriate
// metrics are recorded upon destruction.
TEST_F(RequestServiceTest, RecordNumRequestsPerDocumentMetric) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
// First auth request.
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
// Abort the first auth request.
federated_auth_request_impl_->CancelTokenRequest();
WaitForCurrentAuthRequest();
RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
FederatedAuthRequestResult::kCanceled,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
CheckAuthExpectations(configuration, expectations);
// Reset test classes for second auth request.
SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
auth_helper_ = std::make_unique<AuthRequestCallbackHelper>();
// Second auth request.
configuration.accounts_dialog_action = AccountsDialogAction::kClose;
expectations = {RequestTokenStatus::kError,
FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
// Check that the appropriate metrics are recorded upon destruction.
federated_auth_request_impl_->ResetAndDeleteThis();
ukm_loop.Run();
// Both requests should have been counted.
histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 2,
1);
// Check for RP-keyed UKM presence.
ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
}
// Test that an error dialog is shown when the token response is invalid.
TEST_F(RequestServiceTest, InvalidResponseErrorDialogShown) {
MockConfiguration configuration = kConfigurationValid;
ErrorDialogType error_dialog_type = ErrorDialogType::kGenericEmptyWithoutUrl;
TokenResponseType token_response_type = TokenResponseType::
kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
configuration.token_response.parse_status =
ParseStatus::kInvalidResponseError;
configuration.error_dialog_type = error_dialog_type;
configuration.token_response_type = token_response_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenInvalidResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
error_dialog_type, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Error.ErrorDialogResult",
ErrorDialogResult::kCloseWithoutMoreDetails, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
token_response_type, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Error.ErrorUrlType", 0);
ExpectUKMPresence("Error.ErrorDialogType");
ExpectUKMPresence("Error.ErrorDialogResult");
ExpectUKMPresence("Error.TokenResponseType");
ExpectNoUKMPresence("Error.ErrorUrlType");
}
// Test that an error dialog is shown when the token response is missing.
TEST_F(RequestServiceTest, NoResponseErrorDialogShown) {
MockConfiguration configuration = kConfigurationValid;
ErrorDialogType error_dialog_type = ErrorDialogType::kGenericEmptyWithoutUrl;
TokenResponseType token_response_type = TokenResponseType::
kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
configuration.token_response.parse_status = ParseStatus::kNoResponseError;
configuration.error_dialog_type = error_dialog_type;
configuration.token_response_type = token_response_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenNoResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
error_dialog_type, 1);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Error.ErrorDialogResult",
ErrorDialogResult::kCloseWithoutMoreDetails, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
token_response_type, 1);
histogram_tester_.ExpectTotalCount("Blink.FedCm.Error.ErrorUrlType", 0);
ExpectUKMPresence("Error.ErrorDialogType");
ExpectUKMPresence("Error.ErrorDialogResult");
ExpectUKMPresence("Error.TokenResponseType");
ExpectNoUKMPresence("Error.ErrorUrlType");
}
// Test that the error UI has proper url set.
TEST_F(RequestServiceTest, ErrorUrlDisplayedWithProperUrl) {
MockConfiguration configuration = kConfigurationValid;
ErrorDialogType error_dialog_type = ErrorDialogType::kGenericEmptyWithUrl;
TokenResponseType token_response_type = TokenResponseType::
kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
ErrorUrlType error_url_type = ErrorUrlType::kCrossOriginSameSite;
configuration.token_error =
TokenError(/*code=*/"", GURL("https://foo.idp.example/error"));
configuration.error_dialog_type = error_dialog_type;
configuration.token_response_type = token_response_type;
configuration.error_url_type = error_url_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenIdpErrorResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
EXPECT_EQ(dialog_controller_state_.token_error->url,
GURL("https://foo.idp.example/error"));
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
error_dialog_type, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogResult",
ErrorDialogResult::kCloseWithMoreDetails,
1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
token_response_type, 1);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorUrlType",
error_url_type, 1);
ExpectUKMPresence("Error.ErrorDialogType");
ExpectUKMPresence("Error.ErrorDialogResult");
ExpectUKMPresence("Error.TokenResponseType");
ExpectUKMPresenceInternal("Error.ErrorUrlType", FedCmIdpEntry::kEntryName);
}
// Test that permission is embargoed upon closing a mismatch dialog.
TEST_F(RequestServiceTest, IdpSigninStatusCloseMismatchEmbargo) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
configuration.idp_signin_status_mismatch_dialog_action =
IdpSigninStatusMismatchDialogAction::kClose;
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.count(
main_test_rfh()->GetLastCommittedOrigin()));
}
// Test that permission is not embargoed upon closing an IDP sign-in flow
// pop-up.
TEST_F(RequestServiceTest, IdpSigninStatusClosePopupEmbargo) {
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
configuration.idp_signin_status_mismatch_dialog_action =
IdpSigninStatusMismatchDialogAction::kClosePopup;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kUiDismissedNoEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.empty());
}
// Test that no registered IdP is added without a registry requested.
TEST_F(RequestServiceTest, MaybeAddRegisteredProvidersEmptyList) {
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
MaybeAddRegisteredProviders(providers);
EXPECT_TRUE(result.empty());
}
// Test that no registered IdP with only named providers requested.
TEST_F(RequestServiceTest, MaybeAddRegisteredProvidersNamed) {
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
providers.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
/*is_registered=*/false));
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
MaybeAddRegisteredProviders(providers);
// Expects the vector to be the same.
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
/*is_registered=*/false));
EXPECT_EQ(expected, result);
}
// Test that a registered provider is added.
TEST_F(RequestServiceTest, MaybeAddRegisteredProvidersAdded) {
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
providers.emplace_back(NewRegisteredIdP(kClientId));
std::vector<GURL> registry;
registry.emplace_back("https://idp.example");
EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
.WillOnce(Return(registry));
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
MaybeAddRegisteredProviders(providers);
// Expects that the registered IdP gets replaced by a named IdP.
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
/*is_registered=*/true));
EXPECT_EQ(expected, result);
}
// Test that all registered IdPs are expanded.
TEST_F(RequestServiceTest,
MaybeAddRegisteredProvidersAllRequestsForRegisteredIdPsAreExpanded) {
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
providers.emplace_back(NewRegisteredIdP(kClientId));
providers.emplace_back(NewRegisteredIdP(kClientId));
std::vector<GURL> registry;
registry.emplace_back("https://idp.example");
EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
.WillOnce(Return(registry));
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
MaybeAddRegisteredProviders(providers);
// Expects that the registered IdP gets replaced by a named IdP.
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
/*is_registered=*/true));
expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
/*is_registered=*/true));
EXPECT_EQ(expected, result);
}
// Test that the registry can add two idps.
TEST_F(RequestServiceTest, MaybeAddRegisteredProvidersTwoRegisteredIdPs) {
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
providers.emplace_back(NewRegisteredIdP(kClientId));
std::vector<GURL> registry;
registry.emplace_back("https://idp1.example");
registry.emplace_back("https://idp2.example");
EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
.WillOnce(Return(registry));
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
MaybeAddRegisteredProviders(providers);
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
expected.emplace_back(NewNamedIdP(GURL("https://idp2.example"), kClientId,
/*is_registered=*/true));
expected.emplace_back(NewNamedIdP(GURL("https://idp1.example"), kClientId,
/*is_registered=*/true));
EXPECT_EQ(expected, result);
}
// Test that registered idps are inserted inline.
TEST_F(RequestServiceTest, MaybeAddRegisteredProvidersInsertedInline) {
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
providers.emplace_back(NewNamedIdP(GURL("https://idp1.example"), kClientId,
/*is_registered=*/false));
providers.emplace_back(NewRegisteredIdP(kClientId));
providers.emplace_back(NewNamedIdP(GURL("https://idp2.example"), kClientId,
/*is_registered=*/false));
std::vector<GURL> registry;
registry.emplace_back("https://idp-registered1.example");
registry.emplace_back("https://idp-registered2.example");
EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
.WillOnce(Return(registry));
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
MaybeAddRegisteredProviders(providers);
// Expects that the registered IdP gets replaced by a named IdP.
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
expected.emplace_back(NewNamedIdP(GURL("https://idp1.example"), kClientId,
/*is_registered=*/false));
expected.emplace_back(NewNamedIdP(GURL("https://idp-registered2.example"),
kClientId, /*is_registered=*/true));
expected.emplace_back(NewNamedIdP(GURL("https://idp-registered1.example"),
kClientId, /*is_registered=*/true));
expected.emplace_back(NewNamedIdP(GURL("https://idp2.example"), kClientId,
/*is_registered=*/false));
EXPECT_EQ(expected, result);
}
// Test that error dialog type metrics are recorded.
TEST_F(RequestServiceTest, ErrorDialogTypeMetrics) {
MockConfiguration configuration = kConfigurationValid;
ErrorDialogType error_dialog_type = ErrorDialogType::kInvalidRequestWithUrl;
configuration.token_error = TokenError(/*code=*/"invalid_request",
GURL("https://foo.idp.example/error"));
configuration.error_dialog_type = error_dialog_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenIdpErrorResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
base::RunLoop().RunUntilIdle();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
error_dialog_type, 1);
ExpectUKMPresence("Error.ErrorDialogType");
}
// Test that error dialog result metrics are recorded.
TEST_F(RequestServiceTest, ErrorDialogResultMetrics) {
MockConfiguration configuration = kConfigurationValid;
configuration.token_error =
TokenError(/*code=*/"", GURL("https://foo.idp.example/error"));
configuration.error_dialog_action = ErrorDialogAction::kGotIt;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenIdpErrorResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
base::RunLoop().RunUntilIdle();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogResult",
ErrorDialogResult::kGotItWithMoreDetails,
1);
ExpectUKMPresence("Error.ErrorDialogResult");
}
// Test that token response type metrics are recorded.
TEST_F(RequestServiceTest, TokenResponseTypeMetrics) {
MockConfiguration configuration = kConfigurationValid;
TokenResponseType token_response_type = TokenResponseType::
kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived;
configuration.token_error = TokenError(/*code=*/"invalid_request",
GURL("https://foo.idp.example/error"));
configuration.token_response_type = token_response_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenIdpErrorResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
base::RunLoop().RunUntilIdle();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
token_response_type, 1);
ExpectUKMPresence("Error.TokenResponseType");
}
// Test that error url type metrics are recorded.
TEST_F(RequestServiceTest, ErrorUrlTypeMetrics) {
MockConfiguration configuration = kConfigurationValid;
ErrorUrlType error_url_type = ErrorUrlType::kCrossOriginSameSite;
configuration.token_error = TokenError(/*code=*/"invalid_request",
GURL("https://foo.idp.example/error"));
configuration.error_url_type = error_url_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenIdpErrorResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
base::RunLoop().RunUntilIdle();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorUrlType",
error_url_type, 1);
ExpectUKMPresenceInternal("Error.ErrorUrlType", FedCmIdpEntry::kEntryName);
}
// Test that cross-site URL fails the request with the appropriate devtools
// issue.
TEST_F(RequestServiceTest, CrossSiteErrorDialogDevtoolsIssue) {
MockConfiguration configuration = kConfigurationValid;
ErrorUrlType error_url_type = ErrorUrlType::kCrossSite;
configuration.token_error = TokenError(
/*code=*/"invalid_request", GURL("https://cross-site.example/error"));
configuration.error_url_type = error_url_type;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kIdTokenCrossSiteIdpErrorResponse,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
}
// Test that the account UI is not displayed if FedCM is disabled after accounts
// fetch.
TEST_F(RequestServiceTest,
AccountUiNotDisplayedIfFedCmDisabledAfterAccountsFetch) {
test_api_permission_delegate_->permission_override_for_nth_ = std::make_pair(
/*override the nth invocation=*/2,
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_EMBARGO));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kDisabledInSettings,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
EXPECT_FALSE(did_show_accounts_dialog());
}
TEST_F(RequestServiceTest, DomainHintInLoginUrl) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kDomainHint;
// Need to have sign in status set to signed in to prompt login url
// computations.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
GURL login_url;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
login_url = url;
return modal.get();
}));
RunAuthDontWaitForCallback(parameters, kConfigurationValid);
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
SimulateLoginToIdP();
std::string expected_url = kIdpLoginUrl;
expected_url += "?domain_hint=domain%40corp.com";
EXPECT_EQ(login_url, expected_url);
}
TEST_F(RequestServiceTest, LoginHintInLoginUrl) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = "hint";
// Need to have sign in status set to signed in to prompt login url
// computations.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
GURL login_url;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
login_url = url;
return modal.get();
}));
RunAuthDontWaitForCallback(parameters, kConfigurationValid);
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
SimulateLoginToIdP();
std::string expected_url = kIdpLoginUrl;
expected_url += "?login_hint=hint";
EXPECT_EQ(login_url, expected_url);
}
TEST_F(RequestServiceTest, DomainHintAndLoginHintInLoginUrl) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kDomainHint;
parameters.identity_providers[0].login_hint = "hint";
// Need to have sign in status set to signed in to prompt login url
// computations.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
GURL login_url;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
login_url = url;
return modal.get();
}));
RunAuthDontWaitForCallback(parameters, kConfigurationValid);
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
SimulateLoginToIdP();
std::string expected_url = kIdpLoginUrl;
expected_url += "?login_hint=hint&domain_hint=domain%40corp.com";
EXPECT_EQ(login_url, expected_url);
}
TEST_F(RequestServiceTest, DomainHintAndLoginHintInLoginUrlWithQuery) {
RequestParameters parameters = kDefaultRequestParameters;
// Testing that spaces are transformed in the url.
parameters.identity_providers[0].domain_hint = "domain hint";
parameters.identity_providers[0].login_hint = "login hint";
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.idp_login_url += "?q=1&t=2";
// Need to have sign in status set to signed in to prompt login url
// computations.
test_permission_delegate_
->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
auto dialog_controller =
std::make_unique<TestDialogController>(kConfigurationValid);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
GURL login_url;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
login_url = url;
return modal.get();
}));
RunAuthDontWaitForCallback(parameters, configuration);
EXPECT_FALSE(did_show_accounts_dialog());
EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
SimulateLoginToIdP(
configuration.idp_info[kProviderUrlFull].config.idp_login_url);
std::string expected_url = kIdpLoginUrl;
expected_url += "?q=1&t=2&login_hint=login%20hint&domain_hint=domain%20hint";
EXPECT_EQ(login_url, expected_url);
}
TEST_F(RequestServiceTest, DomainHintAddAccount) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].domain_hint = kDomainHint;
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
configuration.idp_info[kProviderUrlFull].accounts =
kSingleAccountWithDomainHint;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
GURL login_url;
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
login_url = url;
return modal.get();
}));
RunAuthDontWaitForCallback(parameters, configuration);
EXPECT_TRUE(did_show_accounts_dialog());
// The `login_url` used when invoking AddAccounts should not include hints.
EXPECT_EQ(login_url, kIdpLoginUrl);
}
// Test that auto re-authn works in active mode.
TEST_F(RequestServiceTest, AutoReauthnInActiveMode) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
for (const auto& idp_info : kConfigurationValid.idp_info) {
ASSERT_EQ(idp_info.second.accounts.size(), 1u);
}
// The following checks work in active mode.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
.WillOnce(Return(false));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
RunAuthDontWaitForCallback(parameters, kConfigurationValid);
ASSERT_EQ(all_accounts_for_display().size(), 1u);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
}
// Test that IdP claimed SignUp takes precedence over browser observed SignIn.
TEST_F(RequestServiceTest,
IdPClaimedSignUpTakesPrecedenceOverBrowserObservedSignIn) {
// Pretend the sharing permission has been granted for all accounts.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), _))
.WillRepeatedly(
Return(std::make_optional<base::Time>(base::Time::Now())));
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
RunAuthDontWaitForCallback(parameters, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
// Accounts are reordered to have sign-in users displayed first.
EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
LoginState::kSignIn);
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
LoginState::kSignUp);
ExpectUkmValue("HasSigninAccount", true);
}
// Test that IdP claimed SignIn does not affect browser observed SignUp.
TEST_F(RequestServiceTest, IdPClaimedSignInDoesNotAffectBrowserObservedSignUp) {
// Pretend the sharing permission has NOT been granted for any account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), _))
.WillRepeatedly(Return(std::nullopt));
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
RunAuthDontWaitForCallback(parameters, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
LoginState::kSignIn);
// This should be kSignUp regardless of IdP's claim.
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
LoginState::kSignUp);
}
// Test that IdP claimed SignIn can affect browser observed SignUp if they have
// third-party cookies access.
TEST_F(RequestServiceTest,
IdPClaimedSignInAffectsBrowserObservedSignUpWith3PCAccess) {
// Pretend the sharing permission has NOT been granted for any account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), _))
.WillRepeatedly(Return(std::nullopt));
// Pretend the IdP was given third-party cookies access.
EXPECT_CALL(*test_api_permission_delegate_,
HasThirdPartyCookiesAccess(_, GURL(kProviderUrlFull),
OriginFromString(kRpUrl)))
.WillRepeatedly(Return(true));
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
RunAuthDontWaitForCallback(parameters, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
LoginState::kSignIn);
// This should be kSignIn to match IdP's claim due to it has 3PC access.
EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
LoginState::kSignIn);
EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
LoginState::kSignUp);
EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
LoginState::kSignUp);
}
// Test active flow is exempted if the FedCM is disabled in settings.
TEST_F(RequestServiceTest, ActiveFlowNotAffectedBySettings) {
test_api_permission_delegate_->permission_override_ =
std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
ApiPermissionStatus::BLOCKED_SETTINGS);
ExpectSuccessfulActiveFlow();
}
// Test active flow is exempted if the FedCM is embargoed in the passive flow.
TEST_F(RequestServiceTest, ActiveFlowNotAffectedByEmbargo) {
test_api_permission_delegate_->RecordDismissAndEmbargo(
OriginFromString(kRpUrl));
ExpectSuccessfulActiveFlow();
}
// Test dismissing UI in active flow does not trigger embargo.
TEST_F(RequestServiceTest, ActiveFlowNotAffectEmbargo) {
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
->SimulateUserActivation();
RequestParameters parameters = kDefaultRequestParameters;
parameters.rp_mode = blink::mojom::RpMode::kActive;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kUiDismissedNoEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kClose;
RunAuthTest(parameters, expectations, configuration);
ukm_loop.Run();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpMode",
static_cast<int>(RpMode::kActive), 1);
ExpectUkmValue("RpMode", static_cast<int>(RpMode::kActive));
EXPECT_TRUE(did_show_accounts_dialog());
EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));
EXPECT_FALSE(test_api_permission_delegate_->embargoed_origins_.count(
main_test_rfh()->GetLastCommittedOrigin()));
}
// Tests that when background text is passed but no background color, the
// background text is ignored.
TEST_F(RequestServiceTest, BrandingWithTextColorAndNoBackgroundColor) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.brand_text_color =
SkColorSetRGB(10, 10, 10);
RequestExpectations expectations = kExpectationSuccess;
expectations.standalone_console_message =
"The FedCM text color is ignored because background color was not "
"provided";
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_EQ(brand_background_color(), std::nullopt);
EXPECT_EQ(brand_text_color(), std::nullopt);
}
// Tests that when background text does not contrast enough with the background
// color, the text color is ignored.
TEST_F(RequestServiceTest, BrandingWithInsufficientContrastTextColor) {
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info[kProviderUrlFull].config.brand_background_color =
SkColorSetRGB(0, 0, 0);
configuration.idp_info[kProviderUrlFull].config.brand_text_color =
SkColorSetRGB(1, 1, 1);
RequestExpectations expectations = kExpectationSuccess;
expectations.standalone_console_message =
"The FedCM text color is ignored because it does not contrast enough "
"with the provided background color";
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
EXPECT_EQ(brand_background_color(), SkColorSetRGB(0, 0, 0));
EXPECT_EQ(brand_text_color(), std::nullopt);
}
class FederatedAuthRequestExampleOrgTest : public RequestServiceTest {
public:
FederatedAuthRequestExampleOrgTest()
: RequestServiceTest("https://rp.example.org/") {}
};
TEST_F(FederatedAuthRequestExampleOrgTest, WellKnownSameSite) {
static const char kExampleOrgProviderUrl[] =
"https://idp.example.org/fedcm.json";
MockIdpInfo idp_info = kDefaultIdentityProviderInfo;
idp_info.well_known.fetch_status.parse_status =
ParseStatus::kInvalidContentTypeError;
idp_info.config.accounts_endpoint = "https://idp.example.org/accounts";
idp_info.config.token_endpoint = "https://idp.example.org/token";
idp_info.config.client_metadata_endpoint =
"https://idp.example.org/client_metadata";
idp_info.config.metrics_endpoint = "";
idp_info.config.idp_login_url = "https://idp.example.org/login";
idp_info.config.disconnect_endpoint = "";
// We make the request from rp.example to idp.example, so it should
// only succeed despite the well-known failure if the flag is on.
MockConfiguration configuration = kConfigurationValid;
configuration.idp_info.clear();
configuration.idp_info[kExampleOrgProviderUrl] = idp_info;
RequestParameters request{kDefaultRequestParameters};
request.identity_providers[0].provider = kExampleOrgProviderUrl;
RequestExpectations expectation = kExpectationSuccess;
expectation.selected_idp_config_url = kExampleOrgProviderUrl;
RunAuthTest(request, expectation, configuration);
}
class TestDialogControllerWithImmediateDismiss : public TestDialogController {
public:
explicit TestDialogControllerWithImmediateDismiss(
MockConfiguration configuration)
: TestDialogController(configuration) {}
~TestDialogControllerWithImmediateDismiss() override = default;
TestDialogControllerWithImmediateDismiss(
const TestDialogControllerWithImmediateDismiss&) = delete;
TestDialogControllerWithImmediateDismiss& operator=(
TestDialogControllerWithImmediateDismiss&) = delete;
bool ShowAccountsDialog(
content::RelyingPartyData rp_data,
const std::vector<IdentityProviderDataPtr>& idp_list,
const std::vector<IdentityRequestAccountPtr>& accounts,
blink::mojom::RpMode rp_mode,
const std::vector<IdentityRequestAccountPtr>& new_accounts,
IdentityRequestDialogController::AccountSelectionCallback on_selected,
IdentityRequestDialogController::LoginToIdPCallback on_add_account,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::AccountsDisplayedCallback
accounts_displayed_callback) override {
std::move(dismiss_callback).Run(DismissReason::kOther);
return false;
}
bool ShowFailureDialog(
const RelyingPartyData& rp_data,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
const IdentityProviderMetadata& idp_metadata,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::LoginToIdPCallback
identity_registry_callback) override {
std::move(dismiss_callback).Run(DismissReason::kOther);
return false;
}
};
// Crash test for crbug.com/328945371.
TEST_F(RequestServiceTest, ImmediateDismiss) {
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kUiDismissedNoEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
SetDialogController(
std::make_unique<TestDialogControllerWithImmediateDismiss>(
kConfigurationValid));
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
histogram_tester_.ExpectTotalCount(
"Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
}
// Tests that dismissing during ShowFailureDialog() does not crash.
TEST_F(RequestServiceTest, FailureDialogImmediateDismiss) {
url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
MockConfiguration configuration = kConfigurationValid;
// Setup IdP sign-in status mismatch.
test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
ParseStatus::kInvalidResponseError;
SetDialogController(
std::make_unique<TestDialogControllerWithImmediateDismiss>(
configuration));
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kUiDismissedNoEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
}
TEST_F(RequestServiceTest, UseOtherAccountAccountOrder) {
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
// User has accounts, kAccountIdNicolas and kAccountIdZach, in that order.
configuration.idp_info[kProviderUrlFull].accounts = kTwoAccounts;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
// The user signs in with account, kAccountIdPeter. User now has
// accounts, kAccountIdNicolas, kAccountIdPeter and kAccountIdZach,
// in that order.
test_network_request_manager_->accounts_list_ = kMultipleAccounts;
federated_auth_request_impl_->OnIdpSigninStatusReceived(
OriginFromString(kProviderUrlFull), true);
return modal.get();
}));
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
ASSERT_EQ(new_accounts().size(), 1u);
// Accounts are reordered to have the most recently signed in account,
// kAccountIdPeter, displayed first.
EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdNicolas);
EXPECT_EQ(all_accounts_for_display()[2]->id, kAccountIdZach);
EXPECT_EQ(new_accounts()[0]->id, kAccountIdPeter);
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
WaitForCurrentAuthRequest();
ukm_loop.Run();
// The first account is selected, and this is a new account.
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount), 1);
ExpectUkmValue(
"UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount));
}
// Tests that when use a different account is used and multiple accounts are
// logged in at once, all the new accounts are part of the new_accounts().
TEST_F(RequestServiceTest, UseOtherAccountMultipleNewAccounts) {
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
// The user signs in with kAccountIdNicolas and kAccountIdZach. User now
// has accounts kAccountId, kAccountIdNicolas, and kAccountIdZach, in
// that order.
test_network_request_manager_->accounts_list_ = {
kSingleAccount[0], kTwoAccounts[0], kTwoAccounts[1]};
federated_auth_request_impl_->OnIdpSigninStatusReceived(
OriginFromString(kProviderUrlFull), true);
return modal.get();
}));
RunAuthDontWaitForCallback(kDefaultRequestParameters, configuration);
ASSERT_EQ(all_accounts_for_display().size(), 3u);
ASSERT_EQ(new_accounts().size(), 2u);
// Accounts are reordered to have the most recently signed in accounts
// displayed first.
EXPECT_EQ(all_accounts_for_display()[0]->id, kTwoAccounts[0]->id);
EXPECT_EQ(all_accounts_for_display()[1]->id, kTwoAccounts[1]->id);
EXPECT_EQ(all_accounts_for_display()[2]->id, kSingleAccount[0]->id);
EXPECT_EQ(new_accounts()[0]->id, kTwoAccounts[0]->id);
EXPECT_EQ(new_accounts()[1]->id, kTwoAccounts[1]->id);
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
WaitForCurrentAuthRequest();
ukm_loop.Run();
// The first account is selected, and this is a new account.
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount), 1);
ExpectUkmValue(
"UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount));
}
TEST_F(RequestServiceTest, UseOtherAccountNoNewAccount) {
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
// No changes to the accounts being logged in. But set the login status
// to force the popup to close.
federated_auth_request_impl_->OnIdpSigninStatusReceived(
OriginFromString(kProviderUrlFull), true);
return modal.get();
}));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
ukm_loop.Run();
// The first account is selected, and this is an existing account since there
// is no new account.
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserSignsInWithExistingAccount),
1);
ExpectUkmValue(
"UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserSignsInWithExistingAccount));
}
TEST_F(RequestServiceTest, UseOtherAccountThenClose) {
MockConfiguration configuration = kConfigurationValid;
configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
auto dialog_controller =
std::make_unique<TestDialogController>(configuration);
base::WeakPtr<TestDialogController> weak_dialog_controller =
dialog_controller->AsWeakPtr();
SetDialogController(std::move(dialog_controller));
std::unique_ptr<WebContents> modal(CreateTestWebContents());
EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
.WillOnce(::testing::WithArg<0>(
[&modal, &weak_dialog_controller, this](const GURL& url) {
// Signs in to new accounts.
test_network_request_manager_->accounts_list_ = {
kSingleAccount[0], kTwoAccounts[0], kTwoAccounts[1]};
federated_auth_request_impl_->OnIdpSigninStatusReceived(
OriginFromString(kProviderUrlFull), true);
// The action is set to close, so the request will be rejected.
weak_dialog_controller->accounts_dialog_action_ =
AccountsDialogAction::kClose;
return modal.get();
}));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
ukm_loop.Run();
// The first account is selected, and this is an existing account since there
// is no new account.
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserDoesNotSignIn), 1);
ExpectUkmValue("UseOtherAccountResult",
static_cast<int>(UseOtherAccountResult::kUserDoesNotSignIn));
}
TEST_F(RequestServiceTest, MultipleIdpSigninDueToHint) {
url::Origin providerOrigin = OriginFromString(kProviderUrlFull);
// Use an invalid login hint so login to IDP URL is shown.
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].login_hint = "not_a_valid_email";
MockConfiguration config = kConfigurationValid;
config.accounts_dialog_action = AccountsDialogAction::kNone;
RunAuthDontWaitForCallback(kDefaultMultiIdpRequestParameters, config);
EXPECT_FALSE(federated_auth_request_impl_->HasUserTriedToSignInToIdp(
GURL(kProviderUrlFull)));
for (int i = 0; i < 5; ++i) {
// First, simulate the user clicking on the sign in to IDP active.
SimulateLoginToIdP();
// Then, simulate user signing into IdP by updating the IdP signin status
// and calling the observer.
test_permission_delegate_->idp_signin_statuses_[providerOrigin] = true;
// We do not update the accounts so they would still be filtered out.
federated_auth_request_impl_->OnIdpSigninStatusReceived(
providerOrigin, /*idp_signin_status=*/true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(federated_auth_request_impl_->HasUserTriedToSignInToIdp(
GURL(kProviderUrlFull)));
}
}
TEST_F(RequestServiceTest, VerifyingDialogCancelExplicitMetrics) {
MockConfiguration config = kConfigurationValid;
config.delay_token_response = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, config);
CloseDialog();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.VerifyingDialogResult",
VerifyingDialogResult::kCancelExplicit,
1);
ExpectUkmValue("VerifyingDialogResult",
static_cast<int>(VerifyingDialogResult::kCancelExplicit));
}
TEST_F(RequestServiceTest, VerifyingDialogCancelAutoReauthnMetrics) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
MockConfiguration config = kConfigurationValid;
config.delay_token_response = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, config);
CloseDialog();
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.VerifyingDialogResult",
VerifyingDialogResult::kCancelAutoReauthn, 1);
ExpectUkmValue("VerifyingDialogResult",
static_cast<int>(VerifyingDialogResult::kCancelAutoReauthn));
}
TEST_F(RequestServiceTest, VerifyingDialogDestroyExplicitMetrics) {
MockConfiguration config = kConfigurationValid;
config.delay_token_response = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, config);
federated_auth_request_impl_->ResetAndDeleteThis();
histogram_tester_.ExpectUniqueSample("Blink.FedCm.VerifyingDialogResult",
VerifyingDialogResult::kDestroyExplicit,
1);
ExpectUkmValue("VerifyingDialogResult",
static_cast<int>(VerifyingDialogResult::kDestroyExplicit));
}
TEST_F(RequestServiceTest, VerifyingDialogDestroyAutoReauthnMetrics) {
// Pretend the sharing permission has been granted for this account.
EXPECT_CALL(
*test_permission_delegate_,
GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
OriginFromString(kProviderUrlFull), kAccountId))
.WillRepeatedly(Return(std::make_optional<base::Time>()));
// Pretend the auto re-authn permission has been granted.
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnSettingEnabled())
.WillOnce(Return(true));
EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
.WillOnce(Return(false));
MockConfiguration config = kConfigurationValid;
config.delay_token_response = true;
RunAuthDontWaitForCallback(kDefaultRequestParameters, config);
federated_auth_request_impl_->ResetAndDeleteThis();
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.VerifyingDialogResult",
VerifyingDialogResult::kDestroyAutoReauthn, 1);
ExpectUkmValue("VerifyingDialogResult",
static_cast<int>(VerifyingDialogResult::kDestroyAutoReauthn));
}
TEST_F(RequestServiceTest, MetricsForThirdPartyCookiesEnabledInSettings) {
// Pretend that third party cookies are enabled in settings.
EXPECT_CALL(*test_api_permission_delegate_,
AreThirdPartyCookiesEnabledInSettings())
.WillOnce(Return(true));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ukm_loop.Run();
ExpectUkmValue("ThirdPartyCookiesStatus",
static_cast<int>(ThirdPartyCookiesStatus::kEnabledInSettings));
ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}
TEST_F(RequestServiceTest, MetricsForThirdPartyCookiesDisabledInSettings) {
// Pretend that third party cookies are disabled in settings.
EXPECT_CALL(*test_api_permission_delegate_,
AreThirdPartyCookiesEnabledInSettings())
.WillOnce(Return(false));
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ukm_loop.Run();
ExpectUkmValue(
"ThirdPartyCookiesStatus",
static_cast<int>(ThirdPartyCookiesStatus::kDisabledInSettings));
ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}
// Tests that we record whether the RP's URL has a path when an accounts dialog
// is shown.
TEST_F(RequestServiceTest, MetricsForRpUrlHasPath) {
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
EXPECT_TRUE(did_show_accounts_dialog());
ExpectUkmValueInEntry("RpUrlHasPath", FedCmEntry::kEntryName, false);
}
// Tests that we record the scroll position when an account is selected.
TEST_F(RequestServiceTest, MetricsForAccountSelectionScrollPosition) {
// Mock the `GetScrollPosition` call.
TestRenderFrameHost* test_rfh =
static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame());
test_rfh->ResetLocalFrame();
FakeLocalFrame local_frame;
local_frame.Init(test_rfh->GetRemoteAssociatedInterfaces());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
ExpectUkmValueInEntry("AccountSelectionScrollPosition",
FedCmEntry::kEntryName, 0);
}
TEST_F(RequestServiceTest, CancelReasonMetrics) {
MockConfiguration config = kConfigurationValid;
config.accounts_dialog_action = AccountsDialogAction::kClose;
RequestExpectations expectations = {
RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, config);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.CancelReason",
IdentityRequestDialogController::DismissReason::kCloseButton, 1);
ExpectUkmValueInEntry(
"CancelReason", FedCmEntry::kEntryName,
static_cast<std::underlying_type_t<
IdentityRequestDialogController::DismissReason>>(
IdentityRequestDialogController::DismissReason::kCloseButton));
}
// Tests that the correct FederatedAuthRequestResult is returned when the
// accounts dialog is suppressed by segmentation platform.
TEST_F(RequestServiceTest, SuppressedBySegmentationPlatform) {
MockConfiguration configuration = kConfigurationValid;
configuration.suppressed_by_segmentation_platform = true;
RequestExpectations expectations = {
RequestTokenStatus::kError,
FederatedAuthRequestResult::kSuppressedBySegmentationPlatform,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, configuration);
}
TEST_F(RequestServiceTest, MetricsForConsecutiveSuccessfulRequests) {
// First successful auth request.
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
// Reset some test helpers so that we can send the second request.
std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
std::make_unique<TestIdpNetworkRequestManager>();
SetNetworkRequestManager(std::move(network_request_manager));
auth_helper_ = std::make_unique<AuthRequestCallbackHelper>();
// Second successful auth request.
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
// There should be two samples, one for each successful request.
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Status.RequestIdToken",
TokenStatus::kSuccessUsingTokenInHttpResponse, 2);
auto CheckUkmMetrics([&](const char* entry_name) {
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
int num_token_status_ukm = 0;
for (const ukm::mojom::UkmEntry* const entry : entries) {
const int64_t* metric =
ukm_recorder()->GetEntryMetric(entry, "Status.RequestIdToken");
if (!metric) {
continue;
}
EXPECT_EQ(static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse),
*metric);
++num_token_status_ukm;
}
ASSERT_EQ(2, num_token_status_ukm);
});
CheckUkmMetrics(FedCmEntry::kEntryName);
CheckUkmMetrics(FedCmIdpEntry::kEntryName);
}
// Test that completing a disconnect request while there is a pending request
// and then later completing the pending request does not crash.
TEST_F(RequestServiceTest, DisconnectWithPendingRequest) {
// Start an auth request.
RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
// Complete a disconnect request.
CompleteDisconnectRequest();
// Complete the auth request.
WaitForCurrentAuthRequest();
CheckAuthExpectations(kConfigurationValid, kExpectationSuccess);
// Auth request and disconnect request metrics should be recorded separately
// with different session IDs.
ExpectTwoUniqueSessionIDs();
}
class FakeLocalFrameWithDelayedCallback : public FakeLocalFrame {
public:
FakeLocalFrameWithDelayedCallback(RenderFrameHost* rfh) {
TestRenderFrameHost* test_rfh = static_cast<TestRenderFrameHost*>(rfh);
test_rfh->ResetLocalFrame();
Init(test_rfh->GetRemoteAssociatedInterfaces());
}
void RunCallback() {
if (!callback_) {
return;
}
std::move(callback_).Run(gfx::Point(0, 0));
}
// FakeLocalFrame:
void GetScrollPosition(GetScrollPositionCallback callback) override {
callback_ = std::move(callback);
}
private:
GetScrollPositionCallback callback_;
};
// Tests that we record the scroll position when an account is selected, even
// when `GetScrollPosition` runs its callback after the RequestService
// object is destroyed.
TEST_F(RequestServiceTest,
MetricsForAccountSelectionScrollPositionDelayedCallback) {
// Mock the `GetScrollPosition` call.
FakeLocalFrameWithDelayedCallback frame(
web_contents()->GetPrimaryMainFrame());
RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
kConfigurationValid);
frame.RunCallback();
base::RunLoop().RunUntilIdle();
ExpectUkmValueInEntry("AccountSelectionScrollPosition",
FedCmEntry::kEntryName, 0);
}
TEST_F(RequestServiceTest, RecordsNoncePresence) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].nonce = "12345";
RunAuthTest(parameters, kExpectationSuccess, kConfigurationValid);
ExpectUkmValue("HasNonce", true);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasNonce", true, 1);
}
TEST_F(RequestServiceTest, NonceAbsenceNoRecord) {
RequestParameters parameters = kDefaultRequestParameters;
parameters.identity_providers[0].nonce = "";
RunAuthTest(parameters, kExpectationSuccess, kConfigurationValid);
ExpectNoUKMPresence("HasNonce");
histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonce", 0);
}
// Tests that LifecycleStateFailureReason is recorded when page is non-primary.
TEST_F(RequestServiceTest, NonPrimaryPageMetrics) {
static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame())
->SetLifecycleState(
RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache);
RequestExpectations expectations = {
RequestTokenStatus::kError,
// When the RenderFrameHost changes on navigation, no console message is
// received, so pass FederatedAuthRequestResult::kSuccess.
FederatedAuthRequestResult::kSuccess,
/*standalone_console_message=*/std::nullopt,
/*selected_idp_config_url=*/std::nullopt};
RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.LifecycleStateFailureReason",
LifecycleStateFailureReason::kInBackForwardCache, 1);
}
} // namespace content::webid