Added rate limiting to Add payment method.

This commit is contained in:
Peter Fabian 2019-12-05 12:03:02 +01:00
parent e34601458b
commit 0d3074a554
3 changed files with 151 additions and 0 deletions

View File

@ -453,6 +453,26 @@ class WC_Form_Handler {
if ( isset( $_POST['woocommerce_add_payment_method'], $_POST['payment_method'] ) ) {
wc_nocache_headers();
// Test rate limit.
$current_user_id = get_current_user_id();
$rate_limit_id = 'add_payment_method_' . $current_user_id;
$delay = (int) apply_filters( 'woocommerce_payment_gateway_add_payment_method_delay', 20 );
if ( WC_Rate_Limiter::retried_too_soon( $rate_limit_id ) ) {
wc_add_notice(
/* translators: %d number of seconds */
_n(
'You cannot add a new payment method so soon after the previous one. Please wait for %d second.',
'You cannot add a new payment method so soon after the previous one. Please wait for %d seconds.',
$delay,
'woocommerce' ),
'error'
);
return;
}
WC_Rate_Limiter::set_rate_limit( $rate_limit_id, $delay);
$nonce_value = wc_get_var( $_REQUEST['woocommerce-add-payment-method-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // @codingStandardsIgnoreLine.
if ( ! wp_verify_nonce( $nonce_value, 'woocommerce-add-payment-method' ) ) {

View File

@ -0,0 +1,79 @@
<?php
/**
* Provide basic rate limiting functionality via WP Options API.
*
* Currently only provides a simple limit by delaying action by X seconds.
*
* Example usage:
*
* When an action runs, call set_rate_limit, e.g.:
*
* WC_Rate_Limiter::set_rate_limit( "{$my_action_name}_{$user_id}", $delay );
*
* This sets a timestamp for future timestamp after which action can run again.
*
*
* Then before running the action again, check if the action is allowed to run, e.g.:
*
* if ( WC_Rate_Limiter::retried_too_soon( "{$my_action_name}_{$user_id}" ) ) {
* add_notice( 'Sorry, too soon!' );
* }
*
* @package WooCommerce/Classes
* @version 3.9.0
* @since 3.9.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Rate limit class.
*/
class WC_Rate_Limiter {
/**
* Constructs Option name from action identifier.
*
* @param $id
* @return string
*/
public static function storage_id( $id ) {
return 'woocommerce_rate_limit_' . $id;
}
/**
* Returns true if the action is not allowed to be run by the rate limiter yet, false otherwise.
*
* @param $id Identifier for the action
* @return bool
*/
public static function retried_too_soon( $id ) {
$next_try_allowed_at = get_option( self::storage_id( $id ) );
// No record of action running, so action is allowed to run.
if ( false === $next_try_allowed_at ) {
return false;
}
// Before allowed next run, retry not allowed yet.
if ( time() <= $next_try_allowed_at ) {
return true;
}
// After allowed next run, retry allowed.
return false;
}
/**
* Sets the rate limit delay in seconds for action with identifier $id.
*
* @param $id Identifier for the action.
* @param $delay Delay in seconds.
* @return bool True if the option setting was successful, false otherwise.
*/
public static function set_rate_limit( $id, $delay ) {
$option_name = self::storage_id( $id );
$next_try_allowed_at = time() + $delay;
return update_option( $option_name, $next_try_allowed_at );
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* File for the WC_Rate_Limiter class.
*
* @package WooCommerce\Tests\Util
*/
/**
* Test class for WC_Rate_Limiter.
* @since 3.9.0
*/
class WC_Tests_Rate_Limiter extends WC_Unit_Test_Case {
/**
* Run setup code for unit tests.
*/
public function setUp() {
parent::setUp();
}
/**
* Run tear down code for unit tests.
*/
public function tearDown() {
parent::tearDown();
}
/**
* Test setting the limit and running rate limited actions.
*
* @since 2.6.0
*/
public function test_rate_limit_limits() {
$action_identifier = 'action_1';
$user_1_id = 10;
$user_2_id = 15;
$rate_limit_id_1 = $action_identifier . $user_1_id;
$rate_limit_id_2 = $action_identifier . $user_2_id;
WC_Rate_Limiter::set_rate_limit( $rate_limit_id_1, 1 );
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon allowed action to run too soon.' );
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user.' );
sleep(1);
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon did not allow action to run after the designated delay.' );
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user.' );
}
}