Skip to content

Commit

Permalink
[SPC] Add Secure Payment Confirmation opt out WPT
Browse files Browse the repository at this point in the history
Adds chromedriver support and web platform test for opt-out for SPC. The
WPT passes locally with SPC opt out enabled (currently disabled behind a
flag).

There's a small change in the SPC MVC logic, since opt-out can now
happen by calling SPCController::OnOptOut directly, which resulted in
the dialog view calling back to OnCancel when the dialog closes since it
didn't know opt-out was programmatically invoked. This is fixed by
having the controller set a bit on the model which the view checks.

Spec PR: w3c/secure-payment-confirmation#215

Bug: 1385865
Change-Id: I777e92b5110785415de68ef9f0497905598e4897
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4030688
Commit-Queue: Nick Burris <nburris@chromium.org>
Reviewed-by: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1073462}
  • Loading branch information
nickburris authored and Chromium LUCI CQ committed Nov 18, 2022
1 parent d7c860a commit 56413f8
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 13 deletions.
3 changes: 3 additions & 0 deletions chrome/browser/devtools/protocol/page_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ protocol::Response PageHandler::SetSPCTransactionMode(
} else if (mode ==
protocol::Page::SetSPCTransactionMode::ModeEnum::Autoreject) {
spc_mode = payments::SPCTransactionMode::AUTOREJECT;
} else if (mode ==
protocol::Page::SetSPCTransactionMode::ModeEnum::Autooptout) {
spc_mode = payments::SPCTransactionMode::AUTOOPTOUT;
} else if (mode != protocol::Page::SetSPCTransactionMode::ModeEnum::None) {
return protocol::Response::ServerError("Unrecognized mode value");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ void SecurePaymentConfirmationDialogView::OnDialogClosed() {
// WebAuthn dialog after clicking 'Verify', or when the user chooses to
// opt-out. We should only run the cancellation callback in the former case;
// in the latter the opt-out callback will trigger from OnOptOutClicked.
if (!opt_out_clicked_) {
if (!model_->opt_out_clicked()) {
std::move(cancel_callback_).Run();
RecordAuthenticationDialogResult(
SecurePaymentConfirmationAuthenticationDialogResult::kClosed);
Expand All @@ -163,8 +163,6 @@ void SecurePaymentConfirmationDialogView::OnDialogClosed() {
}

void SecurePaymentConfirmationDialogView::OnOptOutClicked() {
opt_out_clicked_ = true;

if (observer_for_test_) {
observer_for_test_->OnOptOutClicked();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ class SecurePaymentConfirmationDialogView
// InitChildViews, but is only marked visible if opt-out was requested.
raw_ptr<views::StyledLabel> opt_out_view_ = nullptr;

// Tracks whether or not the user clicked the 'Opt Out' button to close the
// transaction dialog. Necessary to distinguish between a cancellation and
// opt-out in OnDialogClosed.
bool opt_out_clicked_ = false;

base::WeakPtrFactory<SecurePaymentConfirmationDialogView> weak_ptr_factory_{
this};
};
Expand Down
1 change: 1 addition & 0 deletions components/payments/content/payment_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum class SPCTransactionMode {
NONE,
AUTOACCEPT,
AUTOREJECT,
AUTOOPTOUT,
};

// This class manages the interaction between the renderer (through the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ void SecurePaymentConfirmationController::
if (request_->spc_transaction_mode() != SPCTransactionMode::NONE) {
if (request_->spc_transaction_mode() == SPCTransactionMode::AUTOACCEPT) {
OnConfirm();
} else if (request_->spc_transaction_mode() ==
SPCTransactionMode::AUTOOPTOUT) {
OnOptOut();
} else {
OnCancel();
}
Expand Down Expand Up @@ -243,6 +246,9 @@ void SecurePaymentConfirmationController::OnCancel() {
}

void SecurePaymentConfirmationController::OnOptOut() {
// Set the opt out clicked state on the model so that the view knows not to
// call back to OnCancel when the dialog is closed.
model_.set_opt_out_clicked(true);
CloseDialog();

if (!request_)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ class SecurePaymentConfirmationModel {
void set_opt_out_link_label(const std::u16string& opt_out_link_label) {
opt_out_link_label_ = opt_out_link_label;
}
bool opt_out_clicked() const { return opt_out_clicked_; }
void set_opt_out_clicked(const bool opt_out_clicked) {
opt_out_clicked_ = opt_out_clicked;
}

// Relying Party id (origin); used in the opt out dialog.
const std::u16string& relying_party_id() const { return relying_party_id_; }
Expand Down Expand Up @@ -182,6 +186,7 @@ class SecurePaymentConfirmationModel {
bool opt_out_visible_ = false;
std::u16string opt_out_label_;
std::u16string opt_out_link_label_;
bool opt_out_clicked_ = false;

std::u16string relying_party_id_;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8123,6 +8123,7 @@ domain Page
none
autoaccept
autoreject
autooptout

# Generates a report for testing.
experimental command generateTestReport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ PASS web API-created TypeError (structuredClone())
PASS web API-created TypeError (worker)
PASS web API-created TypeError (cross-site iframe)
PASS web API-created TypeError (same-origin iframe)
FAIL web API-created DOMException (structuredClone()) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:41:19)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2590:25)\n at test (http://web-platform.test:8001/resources/testharness.js:628:30)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:40:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (worker) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:53:19)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2590:25)\n at async_test (http://web-platform.test:8001/resources/testharness.js:676:34)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:52:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (cross-site iframe) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at iframeTest (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:72:19)\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:99:5)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2590:25)\n at async_test (http://web-platform.test:8001/resources/testharness.js:676:34)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:96:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (same-origin iframe) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at iframeTest (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:72:19)\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:104:5)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2590:25)\n at async_test (http://web-platform.test:8001/resources/testharness.js:676:34)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:102:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (structuredClone()) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:41:19)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2591:25)\n at test (http://web-platform.test:8001/resources/testharness.js:628:30)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:40:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (worker) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:53:19)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2591:25)\n at async_test (http://web-platform.test:8001/resources/testharness.js:676:34)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:52:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (cross-site iframe) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at iframeTest (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:72:19)\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:99:5)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2591:25)\n at async_test (http://web-platform.test:8001/resources/testharness.js:676:34)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:96:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
FAIL web API-created DOMException (same-origin iframe) assert_equals: expected (string) "Error: Failed to execute 'createElement' on 'Document': The tag name provided ('') is not a valid name.\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:33:14\n at iframeTest (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:72:19)\n at Test.<anonymous> (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:104:5)\n at Test.step (http://web-platform.test:8001/resources/testharness.js:2591:25)\n at async_test (http://web-platform.test:8001/resources/testharness.js:676:34)\n at stackTests (http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:102:3)\n at http://web-platform.test:8001/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js:31:1" but got (undefined) undefined
Harness: the test ran to completion.

Original file line number Diff line number Diff line change
Expand Up @@ -2246,7 +2246,8 @@
ReadOnlyError: 0,
VersionError: 0,
OperationError: 0,
NotAllowedError: 0
NotAllowedError: 0,
OptOutError: 0
};

var code_name_map = {};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This is a testharness.js-based test.
FAIL SPC opt-out returns OptOutError promise_test: Unhandled rejection with value: object "Error: unimplemented"
Harness: the test ran to completion.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test for the 'secure-payment-confirmation' payment method authentication - user opt out case</title>
<link rel="help" href="https://w3c.github.io/secure-payment-confirmation/#sctn-user-opt-out">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="utils.sub.js"></script>
<script>
'use strict';

promise_test(async t => {
const authenticator = await window.test_driver.add_virtual_authenticator(
AUTHENTICATOR_OPTS);
t.add_cleanup(() => {
return window.test_driver.remove_virtual_authenticator(authenticator);
});

await window.test_driver.set_spc_transaction_mode("autooptout");
t.add_cleanup(() => {
return window.test_driver.set_spc_transaction_mode("none");
});


const credential = await createCredential();

const challenge = 'server challenge';
const payeeOrigin = 'https://merchant.com';
const displayName = 'Troycard ***1234';
const request = new PaymentRequest([{
supportedMethods: 'secure-payment-confirmation',
data: {
credentialIds: [credential.rawId],
challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)),
rpId: window.location.hostname,
payeeOrigin,
timeout: 60000,
instrument: {
displayName,
icon: ICON_URL,
},
showOptOut: true,
}
}], PAYMENT_DETAILS);

await test_driver.bless('user activation');
return promise_rejects_dom(t, "OptOutError", request.show());
}, 'SPC opt-out returns OptOutError');
</script>

0 comments on commit 56413f8

Please sign in to comment.