mirror of
https://github.com/WordPress/five-for-the-future.git
synced 2025-04-22 02:53:43 +03:00
Email: Allow tokens to be reused when necessary.
This commit is contained in:
parent
469e3437b1
commit
6ae26bf4db
|
@ -92,10 +92,14 @@ function send_email( $to, $subject, $message, $pledge_id ) {
|
||||||
* @param string $action
|
* @param string $action
|
||||||
* @param int $action_page_id The ID of the page that the user will be taken back to, in order to process their
|
* @param int $action_page_id The ID of the page that the user will be taken back to, in order to process their
|
||||||
* confirmation request.
|
* confirmation request.
|
||||||
|
* @param bool $use_once Whether or not the token should be deleted after the first use. Only pass `false`
|
||||||
|
* when the action requires several steps in a flow, rather than a single step. For
|
||||||
|
* instance, be able to 1) view a private pledge; 2) make changes and save them; and
|
||||||
|
* 3) reload the private pledge with the new changes displayed.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function get_authentication_url( $pledge_id, $action, $action_page_id ) {
|
function get_authentication_url( $pledge_id, $action, $action_page_id, $use_once = true ) {
|
||||||
$auth_token = array(
|
$auth_token = array(
|
||||||
/*
|
/*
|
||||||
* This will create a CSPRN and is similar to how `get_password_reset_key()` and
|
* This will create a CSPRN and is similar to how `get_password_reset_key()` and
|
||||||
|
@ -104,6 +108,7 @@ function get_authentication_url( $pledge_id, $action, $action_page_id ) {
|
||||||
'value' => wp_generate_password( TOKEN_LENGTH, false ),
|
'value' => wp_generate_password( TOKEN_LENGTH, false ),
|
||||||
// todo Ideally should encrypt at rest, see https://core.trac.wordpress.org/ticket/24783.
|
// todo Ideally should encrypt at rest, see https://core.trac.wordpress.org/ticket/24783.
|
||||||
'expiration' => time() + ( 2 * HOUR_IN_SECONDS ),
|
'expiration' => time() + ( 2 * HOUR_IN_SECONDS ),
|
||||||
|
'use_once' => $use_once,
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -135,13 +140,16 @@ function get_authentication_url( $pledge_id, $action, $action_page_id ) {
|
||||||
/**
|
/**
|
||||||
* Verify whether or not a given authentication token is valid.
|
* Verify whether or not a given authentication token is valid.
|
||||||
*
|
*
|
||||||
* These tokens are more secure than WordPress' imitation nonces, because they can only be used once, and expire
|
* These tokens are more secure than WordPress' imitation nonces because they cannot be reused[1], and expire
|
||||||
* in a shorter timeframe. Like WP nonces, though, they must be tied to a specific action and post object in order
|
* in a shorter timeframe. Like WP nonces, though, they must be tied to a specific action and post object in order
|
||||||
* to prevent misuse.
|
* to prevent misuse.
|
||||||
*
|
*
|
||||||
* @param $pledge_id
|
* [1] In some cases, tokens can be reused, when that is explicitly required for their flow. For an example, see
|
||||||
* @param $action
|
* the documentation in `get_authentication_url()`.
|
||||||
* @param $unverified_token
|
*
|
||||||
|
* @param int $pledge_id
|
||||||
|
* @param string $action
|
||||||
|
* @param string $unverified_token
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
@ -168,12 +176,10 @@ function is_valid_authentication_token( $pledge_id, $action, $unverified_token )
|
||||||
if ( $valid_token && $valid_token['expiration'] > time() && hash_equals( $valid_token['value'], $unverified_token ) ) {
|
if ( $valid_token && $valid_token['expiration'] > time() && hash_equals( $valid_token['value'], $unverified_token ) ) {
|
||||||
$verified = true;
|
$verified = true;
|
||||||
|
|
||||||
// Tokens should not be reusable, to increase security.
|
// Tokens should not be reusable -- to increase security -- unless explicitly required to fulfill their purpose.
|
||||||
|
if ( $valid_token['use_once'] !== false ) {
|
||||||
delete_post_meta( $pledge_id, TOKEN_PREFIX . $action );
|
delete_post_meta( $pledge_id, TOKEN_PREFIX . $action );
|
||||||
// todo when used to manage pledge, token will probably get deleted when viewing, and then they won't be able to save
|
}
|
||||||
// fix that when create the manage process, though. for now this works for confirming email address.
|
|
||||||
// maye pass a `context` param to this function, either 'view' or 'update', and only delete if context is 'update' ?
|
|
||||||
// make sure view and update functions checks to make sure have valid token, not create though
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $verified;
|
return $verified;
|
||||||
|
|
|
@ -204,4 +204,38 @@ class Test_Email extends WP_UnitTestCase {
|
||||||
$this->assertNotSame( $other_valid_token['value'], self::$valid_token['value'] );
|
$this->assertNotSame( $other_valid_token['value'], self::$valid_token['value'] );
|
||||||
$this->assertSame( false, $verified );
|
$this->assertSame( false, $verified );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::is_valid_authentication_token
|
||||||
|
*/
|
||||||
|
public function test_reusable_token_is_reusable() {
|
||||||
|
$reusable_action = 'manage_pledge';
|
||||||
|
get_authentication_url( self::$valid_pledge->ID, $reusable_action, self::$valid_action_page->ID, false );
|
||||||
|
$reusable_token = get_post_meta( self::$valid_pledge->ID, TOKEN_PREFIX . $reusable_action, true );
|
||||||
|
|
||||||
|
// The token should be usable multiple times.
|
||||||
|
$first_verification = is_valid_authentication_token( self::$valid_pledge->ID, $reusable_action, $reusable_token['value'] );
|
||||||
|
$second_verification = is_valid_authentication_token( self::$valid_pledge->ID, $reusable_action, $reusable_token['value'] );
|
||||||
|
$third_verification = is_valid_authentication_token( self::$valid_pledge->ID, $reusable_action, $reusable_token['value'] );
|
||||||
|
|
||||||
|
$this->assertSame( true, $first_verification );
|
||||||
|
$this->assertSame( true, $second_verification );
|
||||||
|
$this->assertSame( true, $third_verification );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::is_valid_authentication_token
|
||||||
|
*/
|
||||||
|
public function test_expired_reusable_token_rejected() {
|
||||||
|
$reusable_action = 'manage_pledge';
|
||||||
|
get_authentication_url( self::$valid_pledge->ID, $reusable_action, self::$valid_action_page->ID, false );
|
||||||
|
$reusable_token = get_post_meta( self::$valid_pledge->ID, TOKEN_PREFIX . $reusable_action, true );
|
||||||
|
|
||||||
|
$reusable_token['expiration'] = time() - 1;
|
||||||
|
update_post_meta( self::$valid_pledge->ID, TOKEN_PREFIX . $reusable_action, $reusable_token );
|
||||||
|
|
||||||
|
$verified = is_valid_authentication_token( self::$valid_pledge->ID, $reusable_action, $reusable_token['value'] );
|
||||||
|
|
||||||
|
$this->assertSame( false, $verified );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue