Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions inc/checkout/class-checkout.php
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,11 @@ public function process_order() {
* let's check if the user is logged in,
* and if not, let's do that.
*/
$this->login_customer_after_checkout();
$login_result = $this->login_customer_after_checkout();

if (is_wp_error($login_result)) {
return $login_result;
}

/*
* Action time.
Expand Down Expand Up @@ -2402,7 +2406,7 @@ protected function form_has_auto_generate_password(): bool {
* just created in this very request and the credential is not available.
*
* @since 2.6.0
* @return void
* @return \WP_Error|null
*/
protected function login_customer_after_checkout() {

Expand All @@ -2426,7 +2430,24 @@ protected function login_customer_after_checkout() {
);

// Sign in the user as if they used the login form.
wp_signon($user_credentials, is_ssl());
$signed_in_user = wp_signon($user_credentials, is_ssl());

if (is_wp_error($signed_in_user)) {
$login_error_message = wp_strip_all_tags($signed_in_user->get_error_message());

if (empty($login_error_message)) {
$login_error_message = __('Unknown login error.', 'ultimate-multisite');
}

return new \WP_Error(
'checkout_login_failed',
sprintf(
/* translators: %s is the login failure message returned by WordPress or another plugin. */
__('We could not log you in automatically during checkout. Please try again or contact support before continuing. Login error: %s', 'ultimate-multisite'),
$login_error_message
)
);
}

return;
}
Expand Down
67 changes: 65 additions & 2 deletions tests/WP_Ultimo/Checkout/Checkout_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -1697,7 +1697,7 @@
public function test_get_js_validation_rules_is_filterable(): void {

add_filter('wu_checkout_js_validation_rules', function ($rules) {
$rules['custom_js_field'] = [['rule' => 'required', 'param' => null]];

Check warning on line 1700 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

When a multi-item array uses associative keys, each value should start on a new line.
return $rules;
});

Expand Down Expand Up @@ -1851,9 +1851,9 @@
*/
public function test_setup_checkout_sets_already_setup_flag(): void {

$checkout = Checkout::get_instance();

Check warning on line 1854 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 3 spaces but found 4 spaces
$reflection = new \ReflectionClass($checkout);

Check warning on line 1855 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces
$setup_prop = $reflection->getProperty('already_setup');

Check warning on line 1856 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces

if (PHP_VERSION_ID < 80100) {
$setup_prop->setAccessible(true);
Expand Down Expand Up @@ -1902,10 +1902,10 @@
*/
public function test_setup_checkout_initialises_session(): void {

$checkout = Checkout::get_instance();

Check warning on line 1905 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 5 spaces but found 6 spaces
$reflection = new \ReflectionClass($checkout);

Check warning on line 1906 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 3 spaces but found 4 spaces
$session_prop = $this->get_session_prop($reflection);

Check warning on line 1907 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces
$setup_prop = $reflection->getProperty('already_setup');

Check warning on line 1908 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 3 spaces but found 4 spaces

if (PHP_VERSION_ID < 80100) {
$setup_prop->setAccessible(true);
Expand Down Expand Up @@ -1940,8 +1940,8 @@

$setup_prop->setValue($checkout, false);

$_REQUEST['pre-flight'] = '1';

Check warning on line 1943 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 4 spaces but found 5 spaces
$_REQUEST['checkout_form'] = 'some-form';

Check warning on line 1944 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 1 space but found 2 spaces
$_REQUEST['some_field'] = 'some_value';

$checkout->setup_checkout();
Expand Down Expand Up @@ -5010,7 +5010,7 @@
$method = $this->get_login_method($reflection);

$login_fired = false;
add_action('wp_login', function() use (&$login_fired) {
add_action('wp_login', function () use (&$login_fired) {
$login_fired = true;
});

Expand Down Expand Up @@ -5128,7 +5128,7 @@
$_REQUEST['password'] = $password;

$login_fired = false;
add_action('wp_login', function() use (&$login_fired) {
add_action('wp_login', function () use (&$login_fired) {
$login_fired = true;
});

Expand All @@ -5147,6 +5147,69 @@
wp_delete_user($user_id);
}

/**
* Test login_customer_after_checkout returns a visible error when wp_signon fails.
*
* Security plugins can hook into the normal WordPress authentication flow and
* reject the credential round-trip. Checkout must stop instead of silently
* handing the customer to a payment gateway while logged out.
*/
public function test_login_customer_after_checkout_with_password_returns_error_when_signon_fails(): void {

$unique = uniqid('badpw_', true);
$password = 'TestP@ssw0rd!';
$user_id = self::factory()->user->create([
'user_login' => $unique,
'user_pass' => $password,
'user_email' => $unique . '@example.com',
]);

wp_set_current_user(0);
wp_clear_auth_cookie();

$customer = wu_create_customer([
'user_id' => $user_id,
'username' => $unique,
'email' => $unique . '@example.com',
]);

if (is_wp_error($customer)) {
require_once ABSPATH . 'wp-admin/includes/user.php';
wp_delete_user($user_id);
$this->markTestSkipped('Customer creation failed: ' . $customer->get_error_message());
}

$checkout = Checkout::get_instance();
$reflection = new \ReflectionClass($checkout);

$this->inject_customer($checkout, $reflection, $customer);
$this->ensure_session($checkout);

$_REQUEST['password'] = 'WrongP@ssw0rd!';

$login_fired = false;
add_action('wp_login', function () use (&$login_fired) {
$login_fired = true;
});

$method = $this->get_login_method($reflection);
$result = $method->invoke($checkout);

remove_all_actions('wp_login');
unset($_REQUEST['password']);

$this->assertWPError($result);
$this->assertSame('checkout_login_failed', $result->get_error_code());
$this->assertStringContainsString('Login error:', $result->get_error_message());
$this->assertFalse($login_fired, 'wp_login must not fire when wp_signon returns an error');

// Cleanup.
wp_set_current_user(0);
$customer->delete();
require_once ABSPATH . 'wp-admin/includes/user.php';
wp_delete_user($user_id);
}

/**
* Test login_customer_after_checkout handles a customer with user_id = 0 gracefully.
*
Expand Down
Loading