diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc index 24bb1396cebcf..a5e1719104d83 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc @@ -23,6 +23,7 @@ #include "components/autofill/core/browser/browser_autofill_manager.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/data_model/credit_card.h" +#include "components/autofill/core/browser/data_model/iban.h" #include "components/autofill/core/browser/form_data_importer.h" #include "components/autofill/core/browser/payments/local_card_migration_manager.h" #include "components/autofill/core/browser/payments/virtual_card_enrollment_flow.h" @@ -715,6 +716,17 @@ ExtensionFunction::ResponseAction AutofillPrivateGetIbanListFunction::Run() { api::autofill_private::GetIbanList::Results::Create(iban_list))); } +//////////////////////////////////////////////////////////////////////////////// +// AutofillPrivateIsValidIbanFunction + +ExtensionFunction::ResponseAction AutofillPrivateIsValidIbanFunction::Run() { + absl::optional parameters = + api::autofill_private::IsValidIban::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(parameters); + return RespondNow(WithArguments( + autofill::IBAN::IsValid(base::UTF8ToUTF16(parameters->iban_value)))); +} + //////////////////////////////////////////////////////////////////////////////// // AutofillPrivateGetUpiIdListFunction diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.h b/chrome/browser/extensions/api/autofill_private/autofill_private_api.h index 3d207497d1ef5..1ab817f243a06 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.h +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.h @@ -268,6 +268,23 @@ class AutofillPrivateGetIbanListFunction : public ExtensionFunction { ResponseAction Run() override; }; +class AutofillPrivateIsValidIbanFunction : public ExtensionFunction { + public: + AutofillPrivateIsValidIbanFunction() = default; + AutofillPrivateIsValidIbanFunction( + const AutofillPrivateIsValidIbanFunction&) = delete; + AutofillPrivateIsValidIbanFunction& operator=( + const AutofillPrivateIsValidIbanFunction&) = delete; + DECLARE_EXTENSION_FUNCTION("autofillPrivate.isValidIban", + AUTOFILLPRIVATE_ISVALIDIBAN) + + protected: + ~AutofillPrivateIsValidIbanFunction() override = default; + + // ExtensionFunction overrides. + ResponseAction Run() override; +}; + class AutofillPrivateGetUpiIdListFunction : public ExtensionFunction { public: AutofillPrivateGetUpiIdListFunction() = default; diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc index e275fba21d550..84bc3b6e6b1f7 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc @@ -130,4 +130,9 @@ IN_PROC_BROWSER_TEST_P(AutofillPrivateApiTest, removeExistingIban) { EXPECT_EQ(1, user_action_tester.GetActionCount("AutofillIbanDeleted")); } +IN_PROC_BROWSER_TEST_P(AutofillPrivateApiTest, isValidIban) { + base::UserActionTester user_action_tester; + EXPECT_TRUE(RunAutofillSubtest("isValidIban")) << message_; +} + } // namespace extensions diff --git a/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.html b/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.html index 7e6233cff65d5..34024c9e3535d 100644 --- a/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.html +++ b/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.html @@ -35,6 +35,7 @@
[[title_]]
$i18n{cancel} + on-click="onIbanSaveButtonClick_"> $i18n{save}
diff --git a/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.ts b/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.ts index 145bc941854f0..c677f116a924a 100644 --- a/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.ts +++ b/chrome/browser/resources/settings/autofill_page/iban_edit_dialog.ts @@ -23,12 +23,7 @@ import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {getTemplate} from './iban_edit_dialog.html.js'; - -/** - * Regular expression for valid IBAN value. - */ -const IBAN_VALID_REGEX: RegExp = new RegExp( - '^[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}$'); +import {PaymentsManagerImpl, PaymentsManagerProxy} from './payments_manager_proxy.js'; declare global { interface HTMLElementEventMap { @@ -85,6 +80,8 @@ export class SettingsIbanEditDialogElement extends private value_?: string; private nickname_?: string; private title_: string; + private paymentsManager_: PaymentsManagerProxy = + PaymentsManagerImpl.getInstance(); override connectedCallback() { super.connectedCallback(); @@ -94,6 +91,7 @@ export class SettingsIbanEditDialogElement extends this.value_ = this.iban.value; this.nickname_ = this.iban.nickname; } + this.$.saveButton.disabled = true; this.$.dialog.showModal(); } @@ -123,16 +121,24 @@ export class SettingsIbanEditDialogElement extends this.close(); } - private saveIbanEnabled_(): boolean { + private updateSaveIbanButtonEnablement_() { + this.isValidIban().then(isValid => { + this.$.saveButton.disabled = !isValid; + }); + } + + private async isValidIban(): Promise { if (!this.value_) { - return false; + return Promise.resolve(false); } // The save button is enabled if the value of the IBAN is invalid (after // removing all whitespace from it). - const ibanWithoutWhitespace = this.value_.replace(/\s/g, ''); - return !!IBAN_VALID_REGEX.test(ibanWithoutWhitespace!); + const isValid = await this.paymentsManager_.isValidIban( + this.value_!.replace(/\s/g, '')); + return isValid; } + /** * @param nickname of the IBAN, undefined when not set. * @return nickname character length. diff --git a/chrome/browser/resources/settings/autofill_page/payments_manager_proxy.ts b/chrome/browser/resources/settings/autofill_page/payments_manager_proxy.ts index ba075925c0003..444e657c03177 100644 --- a/chrome/browser/resources/settings/autofill_page/payments_manager_proxy.ts +++ b/chrome/browser/resources/settings/autofill_page/payments_manager_proxy.ts @@ -29,6 +29,9 @@ export interface PaymentsManagerProxy { */ getIbanList(): Promise; + /** @param ibanValue Returns true if the given ibanValue is valid. */ + isValidIban(ibanValue: string): Promise; + /** @param guid The GUID of the credit card to remove. */ removeCreditCard(guid: string): void; @@ -106,6 +109,10 @@ export class PaymentsManagerImpl implements PaymentsManagerProxy { return chrome.autofillPrivate.getIbanList(); } + isValidIban(ibanValue: string) { + return chrome.autofillPrivate.isValidIban(ibanValue); + } + removeCreditCard(guid: string) { chrome.autofillPrivate.removeEntry(guid); } diff --git a/chrome/common/extensions/api/autofill_private.idl b/chrome/common/extensions/api/autofill_private.idl index 807c875c0261b..b839b8e74becc 100644 --- a/chrome/common/extensions/api/autofill_private.idl +++ b/chrome/common/extensions/api/autofill_private.idl @@ -245,6 +245,7 @@ namespace autofillPrivate { callback GetCreditCardListCallback = void(CreditCardEntry[] entries); callback GetIbanListCallback = void(IbanEntry[] entries); callback GetUpiIdListCallback = void(DOMString[] entries); + callback IsValidIbanCallback = void(boolean isValid); interface Functions { // Gets currently signed-in user profile info, no value is returned if @@ -310,6 +311,12 @@ namespace autofillPrivate { [supportsPromises] static void getIbanList( GetIbanListCallback callback); + // Returns true if the given `ibanValue` is a valid IBAN. + // `callback`: Callback which will be called with the result of IBAN + // validation. + [supportsPromises] static void isValidIban( + DOMString ibanValue, IsValidIbanCallback callback); + // Clears the data associated with a wallet card which was saved // locally so that the saved copy is masked (e.g., "Card ending // in 1234"). diff --git a/chrome/test/data/extensions/api_test/autofill_private/test.js b/chrome/test/data/extensions/api_test/autofill_private/test.js index 5f98f5e10632e..54878e49ed2f9 100644 --- a/chrome/test/data/extensions/api_test/autofill_private/test.js +++ b/chrome/test/data/extensions/api_test/autofill_private/test.js @@ -22,6 +22,7 @@ var NUMBER = '4111 1111 1111 1111'; var EXP_MONTH = '02'; var EXP_YEAR = '2999'; var IBAN_VALUE = 'AD1400080001001234567890'; +var INVALID_IBAN_VALUE = 'AD14000800010012345678900'; var failOnceCalled = function() { chrome.test.fail(); @@ -524,6 +525,22 @@ var availableTests = [ countryCode: COUNTRY_CODE }, handler1); }, + + function isValidIban() { + var handler1 = function(isValidIban) { + // IBAN_VALUE should be valid, then follow up with invalid value. + chrome.test.assertTrue(isValidIban); + chrome.autofillPrivate.isValidIban(INVALID_IBAN_VALUE, handler2); + } + + var handler2 = function(isValidIban) { + // INVALID_IBAN_VALUE is not valid. + chrome.test.assertFalse(isValidIban); + chrome.test.succeed(); + } + + chrome.autofillPrivate.isValidIban(IBAN_VALUE, handler1); + }, ]; /** @const */ @@ -535,13 +552,14 @@ var TESTS_FOR_CONFIG = { ], 'addNewIbanNoNickname': ['addNewIbanNoNickname'], 'addNewIbanWithNickname': ['addNewIbanWithNickname'], - 'noChangesToExistingIban': ['addNewIbanNoNickname','noChangesToExistingIban'], - 'updateExistingIbanNoNickname': [ - 'addNewIbanNoNickname', 'updateExistingIban_NoNickname'], - 'updateExistingIbanWithNickname': [ - 'addNewIbanNoNickname', 'updateExistingIban_WithNickname'], - 'removeExistingIban': [ - 'addNewIbanNoNickname', 'removeExistingIban'], + 'noChangesToExistingIban': + ['addNewIbanNoNickname', 'noChangesToExistingIban'], + 'updateExistingIbanNoNickname': + ['addNewIbanNoNickname', 'updateExistingIban_NoNickname'], + 'updateExistingIbanWithNickname': + ['addNewIbanNoNickname', 'updateExistingIban_WithNickname'], + 'removeExistingIban': ['addNewIbanNoNickname', 'removeExistingIban'], + 'isValidIban': ['isValidIban'], }; chrome.test.getConfig(function(config) { diff --git a/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts b/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts index 24df134b65867..40bd6e82940e0 100644 --- a/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts +++ b/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts @@ -506,6 +506,7 @@ export class PaymentsManagerExpectations { addedVirtualCards: number = 0; requestedIbans: number = 0; removedIbans: number = 0; + isValidIban: number = 0; } /** @@ -535,6 +536,7 @@ export class TestPaymentsManager extends TestBrowserProxy implements 'removeCreditCard', 'removeIban', 'addVirtualCard', + 'isValidIban', ]); // Set these to have non-empty data. @@ -602,6 +604,11 @@ export class TestPaymentsManager extends TestBrowserProxy implements return Promise.resolve(this.data.ibans); } + isValidIban(_ibanValue: string) { + this.methodCalled('isValidIban'); + return Promise.resolve(true); + } + setIsUserVerifyingPlatformAuthenticatorAvailable(available: boolean|null) { this.isUserVerifyingPlatformAuthenticatorAvailable_ = available; diff --git a/chrome/test/data/webui/settings/payments_section_iban_test.ts b/chrome/test/data/webui/settings/payments_section_iban_test.ts index ae4c04a08f149..cb6d5f3d6c6d0 100644 --- a/chrome/test/data/webui/settings/payments_section_iban_test.ts +++ b/chrome/test/data/webui/settings/payments_section_iban_test.ts @@ -4,7 +4,7 @@ // clang-format off import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; -import {PaymentsManagerImpl, SettingsIbanEditDialogElement} from 'chrome://settings/lazy_load.js'; +import {CrInputElement, PaymentsManagerImpl, SettingsIbanEditDialogElement} from 'chrome://settings/lazy_load.js'; import {CrButtonElement, loadTimeData} from 'chrome://settings/settings.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; import {eventToPromise, whenAttributeIs} from 'chrome://webui-test/test_util.js'; @@ -14,6 +14,16 @@ import {createPaymentsSection, getDefaultExpectations} from './payments_section_ // clang-format on +/** + * Helper function to update IBAN value in the IBAN field. + */ +function updateIbanTextboxValue(valueInput: CrInputElement, value: string) { + valueInput.value = value; + valueInput.dispatchEvent( + new CustomEvent('input', {bubbles: true, composed: true})); +} + + suite('PaymentsSectionIban', function() { setup(function() { document.body.innerHTML = window.trustedTypes!.emptyHTML; @@ -26,7 +36,7 @@ suite('PaymentsSectionIban', function() { }); /** - * Creates the Edit IBAN dialog. + * Creates the Add or Edit IBAN dialog. */ function createIbanDialog(ibanItem: chrome.autofillPrivate.IbanEntry): SettingsIbanEditDialogElement { @@ -34,6 +44,7 @@ suite('PaymentsSectionIban', function() { dialog.iban = ibanItem; document.body.appendChild(dialog); flush(); + dialog.$.saveButton.disabled = false; return dialog; } @@ -135,29 +146,28 @@ suite('PaymentsSectionIban', function() { const saveButton = ibanDialog.$.saveButton; assertTrue(!!saveButton); - // Can't be saved, because there's no value. - assertTrue(saveButton.disabled); - // Add invalid IBAN value. + // Add a valid IBAN value. const valueInput = ibanDialog.$.valueInput; - valueInput.value = '11112222333344445678'; - flush(); - // Can't be saved, because the value of IBAN is invalid. - assertTrue(saveButton.disabled); + updateIbanTextboxValue(valueInput, 'FI1410093000123458'); - // Add valid IBAN value. - valueInput.value = 'FI1410093000123458'; - flush(); - // Can be saved, because the value of IBAN is valid. - assertFalse(saveButton.disabled); + // Type in another valid IBAN value. + updateIbanTextboxValue(valueInput, 'IT60X0542811101000000123456'); const savePromise = eventToPromise('save-iban', ibanDialog); saveButton.click(); const event = await savePromise; assertEquals(undefined, event.detail.guid); - assertEquals('FI1410093000123458', event.detail.value); + assertEquals('IT60X0542811101000000123456', event.detail.value); assertEquals('', event.detail.nickname); + + const paymentsManager = + PaymentsManagerImpl.getInstance() as TestPaymentsManager; + const expectations = getDefaultExpectations(); + expectations.isValidIban = 2; + expectations.listeningCreditCards = 0; + paymentsManager.assertExpectations(expectations); }); test('verifyIbanEntryIsNotEditedAfterCancel', async function() { diff --git a/chrome/test/data/webui/settings/payments_section_interactive_test.ts b/chrome/test/data/webui/settings/payments_section_interactive_test.ts index dab4e7d6e6981..f3dc00e46c6bc 100644 --- a/chrome/test/data/webui/settings/payments_section_interactive_test.ts +++ b/chrome/test/data/webui/settings/payments_section_interactive_test.ts @@ -131,6 +131,7 @@ suite('PaymentsSectionCreditCardEditDialogTest', function() { const ibanDialog = section.shadowRoot!.querySelector('settings-iban-edit-dialog'); assertTrue(!!ibanDialog); + ibanDialog.$.saveButton.disabled = false; return ibanDialog!; } @@ -191,6 +192,7 @@ suite('PaymentsSectionCreditCardEditDialogTest', function() { const ibanDialog = section.shadowRoot!.querySelector('settings-iban-edit-dialog'); assertTrue(!!ibanDialog); + ibanDialog.$.saveButton.disabled = false; return ibanDialog; } diff --git a/chrome/test/data/webui/settings/payments_section_utils.ts b/chrome/test/data/webui/settings/payments_section_utils.ts index 53fe215cffd07..5446dbcd94fe1 100644 --- a/chrome/test/data/webui/settings/payments_section_utils.ts +++ b/chrome/test/data/webui/settings/payments_section_utils.ts @@ -47,6 +47,7 @@ export function getDefaultExpectations(): PaymentsManagerExpectations { expected.addedVirtualCards = 0; expected.requestedIbans = 1; expected.removedIbans = 0; + expected.isValidIban = 0; return expected; } diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index f74be99718d7b..ff9cad3330b70 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1831,6 +1831,7 @@ enum HistogramValue { SMARTCARDPROVIDERPRIVATE_REPORTCONNECTRESULT = 1768, SMARTCARDPROVIDERPRIVATE_REPORTDISCONNECTRESULT = 1769, WMDESKSPRIVATE_GETDESKBYID = 1770, + AUTOFILLPRIVATE_ISVALIDIBAN = 1771, // Last entry: Add new entries above, then run: // tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 27480528f8439..2e46c20b35b44 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -36182,6 +36182,7 @@ Called by update_extension_histograms.py.--> + diff --git a/tools/typescript/definitions/autofill_private.d.ts b/tools/typescript/definitions/autofill_private.d.ts index cada1071cf9c3..4a9c9ddcf54ee 100644 --- a/tools/typescript/definitions/autofill_private.d.ts +++ b/tools/typescript/definitions/autofill_private.d.ts @@ -123,6 +123,7 @@ declare global { params: ValidatePhoneParams): Promise; export function getCreditCardList(): Promise; export function getIbanList(): Promise; + export function isValidIban(ibanValue: string): Promise; export function maskCreditCard(guid: string): void; export function migrateCreditCards(): void; export function logServerCardLinkClicked(): void;