blob: fe40fb7678979d22cbcb16250a0c0fa049e1b7af [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/content/browser/content_identity_credential_delegate.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/form_structure_test_api.h"
#include "components/autofill/core/browser/foundations/test_autofill_client.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "content/public/browser/webid/autofill_source.h"
#include "content/public/browser/webid/identity_request_dialog_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
namespace autofill {
namespace {
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
class MockAutofillSource : public content::webid::AutofillSource {
public:
MockAutofillSource() = default;
~MockAutofillSource() override = default;
MOCK_METHOD(const std::optional<std::vector<IdentityRequestAccountPtr>>,
GetAutofillSuggestions,
(),
(const override));
MOCK_METHOD(void,
NotifyAutofillSuggestionAccepted,
(const GURL& idp,
const std::string& account_id,
bool show_modal,
OnFederatedTokenReceivedCallback callback),
(override));
};
// Creates a generic test account with a mediated IdP by default. To test
// delegated IdP, the caller should update the identity provider's format to
// kSdJwt.
IdentityRequestAccountPtr CreateTestAccount() {
IdentityRequestAccountPtr account =
base::MakeRefCounted<content::IdentityRequestAccount>(
"id", "display_identifier", "display_name", "john@email.com", "John",
"given_name", GURL(), "+1 (234) 567-8910", "username",
/*login_hints=*/std::vector<std::string>(),
/*domain_hints=*/std::vector<std::string>(),
/*labels=*/std::vector<std::string>());
content::IdentityProviderMetadata metadata;
metadata.config_url = GURL("https://idp.example");
content::ClientMetadata client((GURL()), (GURL()), (GURL()), (gfx::Image()));
std::vector<content::IdentityRequestDialogDisclosureField> disclosures;
scoped_refptr<content::IdentityProviderData> identity_provider_data =
base::MakeRefCounted<content::IdentityProviderData>(
"idp.example", metadata, client, blink::mojom::RpContext::kSignIn,
/*format=*/std::nullopt, disclosures, false);
account->identity_provider = identity_provider_data;
return account;
}
class ContentIdentityCredentialDelegateTest : public ::testing::Test {
public:
ContentIdentityCredentialDelegateTest() {
FormData form;
form.set_url(GURL("https://www.foo.com"));
form.set_fields({test::CreateTestFormField(
"unclassifiable label", "unclassifiable name", "unclassifiable value",
FormControlType::kInputText)});
form_structure_ = std::make_unique<FormStructure>(form);
}
TestAutofillClient& client() { return autofill_client_; }
FormStructure& form() { return *form_structure_; }
AutofillField& field() { return *form_structure_->fields().front(); }
private:
base::test::SingleThreadTaskEnvironment task_environment_;
test::AutofillUnitTestEnvironment autofill_test_environment_;
TestAutofillClient autofill_client_;
std::unique_ptr<FormStructure> form_structure_;
};
TEST_F(ContentIdentityCredentialDelegateTest, NoPendingRequest) {
ContentIdentityCredentialDelegate delegate(base::BindLambdaForTesting([]() {
content::webid::AutofillSource* result = nullptr;
return result;
}));
test_api(form()).SetFieldTypes({EMAIL_ADDRESS});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(0ul, suggestions.size());
}
TEST_F(ContentIdentityCredentialDelegateTest, NoAccounts) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(std::nullopt));
test_api(form()).SetFieldTypes({EMAIL_ADDRESS});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(0ul, suggestions.size());
}
TEST_F(ContentIdentityCredentialDelegateTest, EmptyAccounts) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
std::vector<IdentityRequestAccountPtr> accounts = {};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({EMAIL_ADDRESS});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(0ul, suggestions.size());
}
TEST_F(ContentIdentityCredentialDelegateTest, UnsupportedFieldType) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
EXPECT_CALL(mock, GetAutofillSuggestions).Times(0);
test_api(form()).SetFieldTypes({UNKNOWN_TYPE});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(0ul, suggestions.size());
}
TEST_F(ContentIdentityCredentialDelegateTest, GetVerifiedEmailRequest) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
// The delegated flow requires an IdP with a specific format.
account->identity_provider->format = blink::mojom::Format::kSdJwt;
// Use only "email" in the selective disclosure request.
account->identity_provider->disclosure_fields = {
content::IdentityRequestDialogDisclosureField::kEmail};
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({EMAIL_ADDRESS});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(1ul, suggestions.size());
Suggestion suggestion = suggestions[0];
EXPECT_EQ(suggestion.main_text.value, u"john@email.com");
ASSERT_EQ(suggestion.labels.size(), 1ul);
ASSERT_EQ(suggestion.minor_texts.size(), 1ul);
EXPECT_EQ(suggestion.icon, Suggestion::Icon::kEmail);
// Expect the payload to be populated properly.
Suggestion::IdentityCredentialPayload payload =
suggestion.GetPayload<Suggestion::IdentityCredentialPayload>();
EXPECT_EQ(payload.account_id, "id");
EXPECT_EQ(payload.config_url, GURL("https://idp.example"));
// Expect only one field to be available in the payload.
ASSERT_EQ(payload.fields.size(), 1ul);
// Expect that email is previewed/filled because it was requested in the
// conditional request.
ASSERT_TRUE(payload.fields.contains(EMAIL_ADDRESS));
EXPECT_EQ(payload.fields[EMAIL_ADDRESS], u"john@email.com");
// Expect that name isn't previewed/filled because it wasn't requested in the
// conditional request.
EXPECT_FALSE(payload.fields.contains(NAME_FULL));
}
TEST_F(ContentIdentityCredentialDelegateTest, SuggestPhoneNumbers) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
// The delegated flow requires an IdP with a specific format.
account->identity_provider->format = blink::mojom::Format::kSdJwt;
// Use "email" AND "phone-number" in the selective disclosure request.
account->identity_provider->disclosure_fields = {
content::IdentityRequestDialogDisclosureField::kPhoneNumber,
content::IdentityRequestDialogDisclosureField::kEmail};
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({PHONE_HOME_WHOLE_NUMBER});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(1ul, suggestions.size());
Suggestion suggestion = suggestions[0];
EXPECT_EQ(suggestion.main_text.value, u"+1 (234) 567-8910");
ASSERT_EQ(suggestion.minor_texts.size(), 1ul);
// Expect the payload to be populated properly.
Suggestion::IdentityCredentialPayload payload =
suggestion.GetPayload<Suggestion::IdentityCredentialPayload>();
EXPECT_EQ(payload.account_id, "id");
EXPECT_EQ(payload.config_url, GURL("https://idp.example"));
// Expect two fields to be available in the payload: emails and usernames.
ASSERT_EQ(payload.fields.size(), 2ul);
// Expect that email is previewed/filled because it was requested in the
// conditional request.
ASSERT_TRUE(payload.fields.contains(EMAIL_ADDRESS));
EXPECT_EQ(payload.fields[EMAIL_ADDRESS], u"john@email.com");
// Expect that email is previewed/filled because it was requested in the
// conditional request.
ASSERT_TRUE(payload.fields.contains(PHONE_HOME_WHOLE_NUMBER));
EXPECT_EQ(payload.fields[PHONE_HOME_WHOLE_NUMBER], u"+1 (234) 567-8910");
}
TEST_F(ContentIdentityCredentialDelegateTest,
GetSuggestionForFieldThatWasntRequested) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
// The delegated flow requires an IdP with a specific format.
account->identity_provider->format = blink::mojom::Format::kSdJwt;
// Use only "email" in the selective disclosure request.
account->identity_provider->disclosure_fields = {
content::IdentityRequestDialogDisclosureField::kEmail};
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({NAME_FULL});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(0ul, suggestions.size());
}
TEST_F(ContentIdentityCredentialDelegateTest,
GetSuggestionForFieldThatRequestedButIsUnavailable) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
// The delegated flow requires an IdP with a specific format.
account->identity_provider->format = blink::mojom::Format::kSdJwt;
// Set email to an unavailable string.
account->email = "";
// Use only "email" in the selective disclosure request.
account->identity_provider->disclosure_fields = {
content::IdentityRequestDialogDisclosureField::kEmail};
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({EMAIL_ADDRESS});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(0ul, suggestions.size());
}
TEST_F(ContentIdentityCredentialDelegateTest,
GetSuggestionsForDelegatedCredentialAvailableForSignUp) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
// The delegated flow requires an IdP with a specific format.
account->identity_provider->format = blink::mojom::Format::kSdJwt;
account->idp_claimed_login_state =
content::IdentityRequestAccount::LoginState::kSignUp;
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({PASSWORD});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(1ul, suggestions.size());
Suggestion suggestion = suggestions[0];
EXPECT_EQ(suggestion.main_text.value, u"john@email.com");
EXPECT_EQ(suggestion.labels.size(), 1ul);
EXPECT_EQ(suggestion.minor_texts.size(), 0ul);
// Expect the payload to be populated properly.
Suggestion::IdentityCredentialPayload payload =
suggestion.GetPayload<Suggestion::IdentityCredentialPayload>();
EXPECT_EQ(payload.account_id, "id");
EXPECT_EQ(payload.config_url, GURL("https://idp.example"));
// Expect no field to be available in the payload for PASSWORD.
EXPECT_TRUE(payload.fields.empty());
}
TEST_F(ContentIdentityCredentialDelegateTest,
GetSuggestionsForPasswordUnavailableForSignUp) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
account->idp_claimed_login_state =
content::IdentityRequestAccount::LoginState::kSignUp;
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({PASSWORD});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
EXPECT_TRUE(suggestions.empty());
}
TEST_F(ContentIdentityCredentialDelegateTest,
GetSuggestionsForPasswordAvailableForSignIn) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
account->idp_claimed_login_state =
content::IdentityRequestAccount::LoginState::kSignIn;
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({PASSWORD});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(1ul, suggestions.size());
Suggestion suggestion = suggestions[0];
EXPECT_EQ(suggestion.main_text.value, u"john@email.com");
EXPECT_EQ(suggestion.labels.size(), 1ul);
EXPECT_EQ(suggestion.minor_texts.size(), 0ul);
// Expect the payload to be populated properly.
Suggestion::IdentityCredentialPayload payload =
suggestion.GetPayload<Suggestion::IdentityCredentialPayload>();
EXPECT_EQ(payload.account_id, "id");
EXPECT_EQ(payload.config_url, GURL("https://idp.example"));
// Expect no field to be available in the payload for PASSWORD.
EXPECT_TRUE(payload.fields.empty());
}
TEST_F(ContentIdentityCredentialDelegateTest, GetProvidedNameRequest) {
MockAutofillSource mock;
ContentIdentityCredentialDelegate delegate(
base::BindLambdaForTesting([&mock]() {
content::webid::AutofillSource* result = &mock;
return result;
}));
IdentityRequestAccountPtr account = CreateTestAccount();
// The delegated flow requires an IdP with a specific format.
account->identity_provider->format = blink::mojom::Format::kSdJwt;
// Use only "name" in the selective disclosure request.
account->identity_provider->disclosure_fields = {
content::IdentityRequestDialogDisclosureField::kName};
std::vector<IdentityRequestAccountPtr> accounts = {account};
EXPECT_CALL(mock, GetAutofillSuggestions).WillOnce(Return(accounts));
test_api(form()).SetFieldTypes({NAME_FULL});
std::vector<Suggestion> suggestions = delegate.GetVerifiedAutofillSuggestions(
form().ToFormData(), &form(), field(), &field(), client());
ASSERT_EQ(1ul, suggestions.size());
Suggestion suggestion = suggestions[0];
EXPECT_EQ(suggestion.main_text.value, u"John");
EXPECT_EQ(suggestion.labels.size(), 0ul);
EXPECT_EQ(suggestion.minor_texts.size(), 1ul);
EXPECT_EQ(suggestion.icon, Suggestion::Icon::kAccount);
// Expect the payload to be populated properly.
Suggestion::IdentityCredentialPayload payload =
suggestion.GetPayload<Suggestion::IdentityCredentialPayload>();
EXPECT_EQ(payload.account_id, "id");
EXPECT_EQ(payload.config_url, GURL("https://idp.example"));
// Expect only one field to be available in the payload.
ASSERT_EQ(payload.fields.size(), 1ul);
// Expect that email is previewed/filled because it was requested in the
// conditional request.
ASSERT_TRUE(payload.fields.contains(NAME_FULL));
EXPECT_EQ(payload.fields[NAME_FULL], u"John");
}
} // namespace
} // namespace autofill