// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h"
#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/memory/singleton.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/autofill/core/browser/address_data_manager.h"
#import "components/autofill/core/browser/autofill_client.h"
#import "components/autofill/core/browser/autofill_test_utils.h"
#import "components/autofill/core/browser/browser_autofill_manager_test_api.h"
#import "components/autofill/core/browser/data_model/autofill_profile_test_api.h"
#import "components/autofill/core/browser/form_data_importer.h"
#import "components/autofill/core/browser/payments/credit_card_save_manager.h"
#import "components/autofill/core/browser/payments/payments_autofill_client.h"
#import "components/autofill/core/browser/payments/payments_network_interface.h"
#import "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h"
#import "components/autofill/core/browser/payments_data_manager.h"
#import "components/autofill/core/browser/personal_data_manager.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/credit_card_save_manager_test_observer_bridge.h"
#import "components/autofill/ios/browser/ios_test_event_waiter.h"
#import "components/autofill/ios/common/features.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/password_manager_util.h"
#import "components/password_manager/core/browser/password_store/password_store_consumer.h"
#import "components/password_manager/core/browser/password_store/password_store_interface.h"
#import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/scoped_autofill_payment_reauth_module_override.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/mock_reauthentication_module.h"
#import "ios/chrome/test/app/tab_test_util.h"
#import "ios/public/provider/chrome/browser/risk_data/risk_data_api.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/web_state.h"
#import "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#import "services/network/test/test_url_loader_factory.h"
namespace {
const char16_t kExampleUsername[] = u"concrete username";
const char16_t kExamplePassword[] = u"concrete password";
// Gets the current profile password store.
scoped_refptr<password_manager::PasswordStoreInterface>
GetPasswordProfileStore() {
// ServiceAccessType governs behaviour in Incognito: only modifications with
// EXPLICIT_ACCESS, which correspond to user's explicit gesture, succeed.
// This test does not deal with Incognito, and should not run in Incognito
// context. Therefore IMPLICIT_ACCESS is used to let the test fail if in
// Incognito context.
return IOSChromeProfilePasswordStoreFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState(),
ServiceAccessType::IMPLICIT_ACCESS);
}
// This class is used to obtain results from the PasswordStore and hence both
// check the success of store updates and ensure that store has finished
// processing.
class TestStoreConsumer : public password_manager::PasswordStoreConsumer {
public:
void OnGetPasswordStoreResults(
std::vector<std::unique_ptr<password_manager::PasswordForm>> obtained)
override {
obtained_ = std::move(obtained);
}
const std::vector<password_manager::PasswordForm>& GetStoreResults() {
results_.clear();
ResetObtained();
GetPasswordProfileStore()->GetAllLogins(weak_ptr_factory_.GetWeakPtr());
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-result"
base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForFileOperationTimeout, ^bool {
return !AreObtainedReset();
});
#pragma clang diagnostic pop
AppendObtainedToResults();
return results_;
}
private:
// Puts `obtained_` in a known state not corresponding to any PasswordStore
// state.
void ResetObtained() {
obtained_.clear();
obtained_.emplace_back(nullptr);
}
// Returns true if `obtained_` are in the reset state.
bool AreObtainedReset() { return obtained_.size() == 1 && !obtained_[0]; }
void AppendObtainedToResults() {
for (const auto& source : obtained_) {
results_.emplace_back(*source);
}
ResetObtained();
}
// Temporary cache of obtained store results.
std::vector<std::unique_ptr<password_manager::PasswordForm>> obtained_;
// Combination of fillable and blocked credentials from the store.
std::vector<password_manager::PasswordForm> results_;
base::WeakPtrFactory<TestStoreConsumer> weak_ptr_factory_{this};
};
// Saves `form` to the profile password store and waits until the async
// processing is done.
void SaveToPasswordProfileStore(const password_manager::PasswordForm& form) {
GetPasswordProfileStore()->AddLogin(form);
// When we retrieve the form from the store, `in_store` should be set.
password_manager::PasswordForm expected_form = form;
expected_form.in_store = password_manager::PasswordForm::Store::kProfileStore;
// Check the result and ensure PasswordStore processed this.
TestStoreConsumer consumer;
for (const auto& result : consumer.GetStoreResults()) {
if (result == expected_form)
return;
}
}
// Saves an example form in the profile store.
void SaveExamplePasswordFormInProfileStore() {
password_manager::PasswordForm example;
example.username_value = kExampleUsername;
example.password_value = kExamplePassword;
example.url = GURL("https://example.com/");
example.signon_realm = password_manager_util::GetSignonRealm(example.url);
SaveToPasswordProfileStore(example);
}
// Saves an example form in the profile store for the passed URL.
void SaveLocalPasswordForm(const GURL& url) {
password_manager::PasswordForm localForm;
localForm.username_value = kExampleUsername;
localForm.password_value = kExamplePassword;
localForm.url = url;
localForm.signon_realm = password_manager_util::GetSignonRealm(localForm.url);
SaveToPasswordProfileStore(localForm);
}
// Removes all credentials from the profile store.
void ClearProfilePasswordStore() {
GetPasswordProfileStore()->RemoveLoginsCreatedBetween(
FROM_HERE, base::Time(), base::Time(), base::DoNothing());
TestStoreConsumer consumer;
}
// Saves an example profile in the store.
void AddAutofillProfile(autofill::PersonalDataManager* personalDataManager,
bool isAccountProfile) {
autofill::AutofillProfile profile = autofill::test::GetFullProfile();
// If the test profile is already in the store, adding it will be a no-op.
// In that case, early return.
for (const autofill::AutofillProfile* p :
personalDataManager->address_data_manager().GetProfiles()) {
if (p->Compare(profile) == 0) {
return;
}
}
size_t profileCount =
personalDataManager->address_data_manager().GetProfiles().size();
if (isAccountProfile) {
test_api(profile).set_record_type(
autofill::AutofillProfile::RecordType::kAccount);
}
personalDataManager->address_data_manager().AddProfile(profile);
ConditionBlock conditionBlock = ^bool {
return profileCount <
personalDataManager->address_data_manager().GetProfiles().size();
};
CHECK(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForActionTimeout, conditionBlock));
}
} // namespace
namespace autofill {
class VirtualCardEnrollmentManager;
// Helper class that provides access to private members of
// BrowserAutofillManager, FormDataImporter and CreditCardSaveManager. This
// class is friend with some autofill internal classes to access private fields.
class FakeCreditCardServer : public CreditCardSaveManager::ObserverForTest {
public:
static FakeCreditCardServer* SharedInstance() {
return base::Singleton<FakeCreditCardServer>::get();
}
FakeCreditCardServer() {}
~FakeCreditCardServer() override {}
FakeCreditCardServer(const FakeCreditCardServer&) = delete;
FakeCreditCardServer& operator=(const FakeCreditCardServer&) = delete;
// Access the CreditCardSaveManager.
static CreditCardSaveManager* GetCreditCardSaveManager() {
return GetAutofillClient()
.GetFormDataImporter()
->GetCreditCardSaveManager();
}
// Access the VirtualCardEnrollmentManager.
static VirtualCardEnrollmentManager* GetVirtualCardEnrollmentManager() {
return GetAutofillClient()
.GetPaymentsAutofillClient()
->GetVirtualCardEnrollmentManager();
}
// Access the PaymentsNetworkInterface.
static payments::PaymentsNetworkInterface* GetPaymentsNetworkInterface() {
return GetAutofillClient()
.GetPaymentsAutofillClient()
->GetPaymentsNetworkInterface();
}
static AutofillClient& GetAutofillClient() {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
web::WebFramesManager* frames_manager =
autofill::AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
web_state);
web::WebFrame* main_frame = frames_manager->GetMainWebFrame();
DCHECK(web_state);
return AutofillDriverIOS::FromWebStateAndWebFrame(web_state, main_frame)
->GetAutofillManager()
.client();
}
// Delete all failed attempds registered on every cards.
static void ClearCreditCardSaveStrikes() {
GetCreditCardSaveManager()
->GetCreditCardSaveStrikeDatabase()
->ClearAllStrikes();
}
static void ClearVirtualCardEnrollmentStrikes() {
GetVirtualCardEnrollmentManager()->ClearAllStrikesForTesting();
}
// Set the number of failed attempds registered on a card.
static void SetFormFillMaxStrikes(NSString* card_key, int max) {
// The strike key is made of CreditCardSaveStrikeDatabase's project prefix
// and the last 4 digits of the card used in fillAndSubmitForm(), which can
// be found in credit_card_upload_form_address_and_cc.html.
GetCreditCardSaveManager()
->GetCreditCardSaveStrikeDatabase()
->strike_database_->SetStrikeData(base::SysNSStringToUTF8(card_key),
max);
}
// Reset the IOSTestEventWaiter and make it watch `events`.
void ResetEventWaiterForEvents(NSArray* events, base::TimeDelta timeout) {
std::list<CreditCardSaveManagerObserverEvent> events_list;
for (NSNumber* e : events) {
events_list.push_back(
static_cast<CreditCardSaveManagerObserverEvent>([e intValue]));
}
event_waiter_ = std::make_unique<
IOSTestEventWaiter<CreditCardSaveManagerObserverEvent>>(
std::move(events_list), timeout);
}
void OnOfferLocalSave() override {
OnEvent(CreditCardSaveManagerObserverEvent::kOnOfferLocalSaveCalled);
}
void OnDecideToRequestUploadSave() override {
OnEvent(
CreditCardSaveManagerObserverEvent::kOnDecideToRequestUploadSaveCalled);
}
void OnReceivedGetUploadDetailsResponse() override {
OnEvent(CreditCardSaveManagerObserverEvent::
kOnReceivedGetUploadDetailsResponseCalled);
}
void OnSentUploadCardRequest() override {
OnEvent(CreditCardSaveManagerObserverEvent::kOnSentUploadCardRequestCalled);
}
void OnReceivedUploadCardResponse() override {
OnEvent(CreditCardSaveManagerObserverEvent::
kOnReceivedUploadCardResponseCalled);
}
void OnShowCardSavedFeedback() override {
OnEvent(CreditCardSaveManagerObserverEvent::kOnShowCardSavedFeedbackCalled);
}
void OnStrikeChangeComplete() override {
OnEvent(CreditCardSaveManagerObserverEvent::kOnStrikeChangeCompleteCalled);
}
// Triggers `event` on the IOSTestEventWaiter.
bool OnEvent(CreditCardSaveManagerObserverEvent event) {
return event_waiter_->OnEvent(event);
}
// Waits until the expected events are triggered.
bool WaitForEvents() { return event_waiter_->Wait(); }
// Sets the response to payment server requests.
void SetPaymentsResponse(NSString* request, NSString* response, int error) {
test_url_loader_factory_->AddResponse(
base::SysNSStringToUTF8(request), base::SysNSStringToUTF8(response),
static_cast<net::HttpStatusCode>(error));
}
void ClearPaymentsResponses() { test_url_loader_factory_->ClearResponses(); }
void SetPaymentsRiskData(const std::string& risk_data) {
GetCreditCardSaveManager()->OnDidGetUploadRiskData(risk_data);
}
void SetUp() {
test_url_loader_factory_ =
std::make_unique<network::TestURLLoaderFactory>();
// Set up the URL loader factory for the PaymentsNetworkInterface so we can
// intercept those network requests.
shared_url_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
test_url_loader_factory_.get());
payments::PaymentsNetworkInterface* payments_network_interface =
FakeCreditCardServer::GetPaymentsNetworkInterface();
payments_network_interface->set_url_loader_factory_for_testing(
shared_url_loader_factory_);
// Set a fake access token to avoid fetch requests.
payments_network_interface->set_access_token_for_testing(
"fake_access_token");
// Observe actions in CreditCardSaveManager.
CreditCardSaveManager* credit_card_save_manager =
FakeCreditCardServer::GetCreditCardSaveManager();
credit_card_save_manager->SetEventObserverForTesting(this);
}
void TearDown() {
ClearCreditCardSaveStrikes();
CreditCardSaveManager* credit_card_save_manager =
FakeCreditCardServer::GetCreditCardSaveManager();
credit_card_save_manager->SetEventObserverForTesting(nullptr);
event_waiter_.reset();
shared_url_loader_factory_.reset();
test_url_loader_factory_.reset();
}
private:
std::unique_ptr<network::TestURLLoaderFactory> test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
std::unique_ptr<IOSTestEventWaiter<CreditCardSaveManagerObserverEvent>>
event_waiter_;
};
}
@implementation AutofillAppInterface
static std::unique_ptr<ScopedAutofillPaymentReauthModuleOverride>
_scopedReauthModuleOverride;
+ (void)clearProfilePasswordStore {
ClearProfilePasswordStore();
}
+ (void)saveExamplePasswordFormToProfileStore {
SaveExamplePasswordFormInProfileStore();
}
+ (void)savePasswordFormForURLSpec:(NSString*)URLSpec {
SaveLocalPasswordForm(GURL(base::SysNSStringToUTF8(URLSpec)));
}
+ (NSInteger)profilesCount {
autofill::PersonalDataManager* personalDataManager =
[self personalDataManager];
return personalDataManager->address_data_manager().GetProfiles().size();
}
+ (void)clearProfilesStore {
ChromeBrowserState* browserState =
chrome_test_util::GetOriginalBrowserState();
autofill::PersonalDataManager* personalDataManager =
autofill::PersonalDataManagerFactory::GetForBrowserState(browserState);
for (const autofill::AutofillProfile* profile :
personalDataManager->address_data_manager().GetProfiles()) {
personalDataManager->RemoveByGUID(profile->guid());
}
ConditionBlock conditionBlock = ^bool {
return 0 ==
personalDataManager->address_data_manager().GetProfiles().size();
};
CHECK(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForActionTimeout, conditionBlock));
autofill::prefs::SetAutofillProfileEnabled(browserState->GetPrefs(), YES);
}
+ (void)saveExampleProfile {
AddAutofillProfile([self personalDataManager], false);
}
+ (void)saveExampleAccountProfile {
AddAutofillProfile([self personalDataManager], true);
}
+ (NSString*)exampleProfileName {
autofill::AutofillProfile profile = autofill::test::GetFullProfile();
std::u16string name =
profile.GetInfo(autofill::AutofillType(autofill::NAME_FULL),
GetApplicationContext()->GetApplicationLocale());
return base::SysUTF16ToNSString(name);
}
+ (void)clearCreditCardStore {
autofill::PersonalDataManager* personalDataManager =
[self personalDataManager];
for (const autofill::CreditCard* creditCard :
personalDataManager->payments_data_manager().GetCreditCards()) {
// This will not remove server cards, as they have no guid.
personalDataManager->RemoveByGUID(creditCard->guid());
}
ChromeBrowserState* browserState =
chrome_test_util::GetOriginalBrowserState();
autofill::prefs::SetAutofillPaymentMethodsEnabled(browserState->GetPrefs(),
YES);
}
// Clears all server data including server cards.
+ (void)clearAllServerDataForTesting {
[self personalDataManager]
->payments_data_manager()
.ClearAllServerDataForTesting();
}
+ (NSString*)saveLocalCreditCard {
autofill::PersonalDataManager* personalDataManager =
[self personalDataManager];
autofill::CreditCard card = autofill::test::GetCreditCard();
size_t card_count =
personalDataManager->payments_data_manager().GetCreditCards().size();
personalDataManager->payments_data_manager().AddCreditCard(card);
ConditionBlock conditionBlock = ^bool {
return card_count <
personalDataManager->payments_data_manager().GetCreditCards().size();
};
CHECK(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForFileOperationTimeout, conditionBlock));
personalDataManager->NotifyPersonalDataObserver();
return base::SysUTF16ToNSString(card.NetworkAndLastFourDigits());
}
+ (NSInteger)localCreditCount {
return [self personalDataManager]
->payments_data_manager()
.GetCreditCards()
.size();
}
+ (NSString*)saveMaskedCreditCard {
autofill::PersonalDataManager* personalDataManager =
[self personalDataManager];
autofill::CreditCard card =
autofill::test::WithCvc(autofill::test::GetMaskedServerCard());
DCHECK(card.record_type() != autofill::CreditCard::RecordType::kLocalCard);
personalDataManager->payments_data_manager().AddServerCreditCardForTest(
std::make_unique<autofill::CreditCard>(card));
personalDataManager->NotifyPersonalDataObserver();
return base::SysUTF16ToNSString(card.NetworkAndLastFourDigits());
}
+ (NSString*)saveMaskedCreditCardEnrolledInVirtualCard {
autofill::PersonalDataManager* personalDataManager =
[self personalDataManager];
size_t card_count =
personalDataManager->payments_data_manager().GetCreditCards().size();
autofill::CreditCard card =
autofill::test::GetMaskedServerCardEnrolledIntoVirtualCardNumber();
CHECK_NE(card.record_type(), autofill::CreditCard::RecordType::kLocalCard);
personalDataManager->payments_data_manager().AddServerCreditCardForTest(
std::make_unique<autofill::CreditCard>(card));
// Confirm card is present in personalDataManager
CHECK(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForFileOperationTimeout, ^bool {
return (personalDataManager->payments_data_manager()
.GetCreditCards()
.size() == card_count + 1);
}));
personalDataManager->NotifyPersonalDataObserver();
return base::SysUTF16ToNSString(card.NetworkAndLastFourDigits());
}
+ (void)setUpFakeCreditCardServer {
autofill::FakeCreditCardServer::SharedInstance()->SetUp();
}
+ (void)tearDownFakeCreditCardServer {
autofill::FakeCreditCardServer::SharedInstance()->TearDown();
}
// Clears the virtual card enrollment strike data.
+ (void)clearVirtualCardEnrollmentStrikes {
autofill::FakeCreditCardServer::SharedInstance()
->ClearVirtualCardEnrollmentStrikes();
}
+ (void)resetEventWaiterForEvents:(NSArray*)events
timeout:(base::TimeDelta)timeout {
autofill::FakeCreditCardServer::SharedInstance()->ResetEventWaiterForEvents(
events, timeout);
}
+ (BOOL)waitForEvents {
return autofill::FakeCreditCardServer::SharedInstance()->WaitForEvents();
}
+ (void)setPaymentsResponse:(NSString*)response
forRequest:(NSString*)request
withErrorCode:(int)error {
return autofill::FakeCreditCardServer::SharedInstance()->SetPaymentsResponse(
request, response, error);
}
+ (void)clearPaymentsResponses {
return autofill::FakeCreditCardServer::SharedInstance()
->ClearPaymentsResponses();
}
+ (void)setAccessToken {
if (autofill::payments::PaymentsNetworkInterface* payments_network_interface =
autofill::FakeCreditCardServer::GetPaymentsNetworkInterface()) {
// Set a fake access token to avoid fetch requests.
payments_network_interface->set_access_token_for_testing(
"fake_access_token");
}
}
+ (void)setFormFillMaxStrikes:(int)max forCard:(NSString*)card {
return autofill::FakeCreditCardServer::SharedInstance()
->SetFormFillMaxStrikes(card, max);
}
+ (void)setPaymentsRiskData:(NSString*)riskData {
return autofill::FakeCreditCardServer::SharedInstance()->SetPaymentsRiskData(
base::SysNSStringToUTF8(riskData));
}
+ (void)considerCreditCardFormSecureForTesting {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
web::WebFramesManager* frames_manager =
autofill::AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
web_state);
web::WebFrame* main_frame = frames_manager->GetMainWebFrame();
test_api(autofill::AutofillDriverIOS::FromWebStateAndWebFrame(web_state,
main_frame)
->GetAutofillManager())
.SetConsiderFormAsSecureForTesting(true);
}
+ (NSString*)paymentsRiskData {
return ios::provider::GetRiskData();
}
+ (void)setUpMockReauthenticationModule {
MockReauthenticationModule* mock_reauthentication_module =
[[MockReauthenticationModule alloc] init];
_scopedReauthModuleOverride =
ScopedAutofillPaymentReauthModuleOverride::MakeAndArmForTesting(
mock_reauthentication_module);
}
+ (void)clearMockReauthenticationModule {
_scopedReauthModuleOverride = nullptr;
}
+ (void)mockReauthenticationModuleCanAttempt:(BOOL)canAttempt {
CHECK(_scopedReauthModuleOverride);
MockReauthenticationModule* mockModule =
base::apple::ObjCCastStrict<MockReauthenticationModule>(
_scopedReauthModuleOverride->module);
mockModule.canAttempt = canAttempt;
}
+ (void)mockReauthenticationModuleExpectedResult:
(ReauthenticationResult)expectedResult {
CHECK(_scopedReauthModuleOverride);
MockReauthenticationModule* mockModule =
base::apple::ObjCCastStrict<MockReauthenticationModule>(
_scopedReauthModuleOverride->module);
mockModule.expectedResult = expectedResult;
}
+ (void)setMandatoryReauthEnabled:(BOOL)enabled {
autofill::PersonalDataManager* personalDataManager =
[self personalDataManager];
personalDataManager->payments_data_manager()
.SetPaymentMethodsMandatoryReauthEnabled(enabled);
}
+ (BOOL)isKeyboardAccessoryUpgradeEnabled {
return IsKeyboardAccessoryUpgradeEnabled();
}
+ (BOOL)isDynamicallyLoadFieldsOnInputEnabled {
return base::FeatureList::IsEnabled(
kAutofillDynamicallyLoadsFieldsForAddressInput);
}
#pragma mark - Private
// The PersonalDataManager instance for the current browser state.
+ (autofill::PersonalDataManager*)personalDataManager {
ChromeBrowserState* browserState =
chrome_test_util::GetOriginalBrowserState();
autofill::PersonalDataManager* personalDataManager =
autofill::PersonalDataManagerFactory::GetForBrowserState(browserState);
personalDataManager->payments_data_manager().SetSyncingForTest(true);
return personalDataManager;
}
@end