Commit 1de8f371 authored by Michael Iseard's avatar Michael Iseard
Browse files

Move secret to abstract entity so that any entity can create one

parent 35619b62
......@@ -3,6 +3,10 @@
namespace Kudos\Entity;
use DateTime;
use Kudos\Exceptions\EntityException;
use Kudos\Service\LoggerService;
use Kudos\Service\MapperService;
use Throwable;
abstract class AbstractEntity implements EntityInterface {
......@@ -18,6 +22,10 @@ abstract class AbstractEntity implements EntityInterface {
* @var DateTime
*/
public $last_updated;
/**
* @var string
*/
public $secret;
/**
* Entity object constructor.
......@@ -34,6 +42,15 @@ abstract class AbstractEntity implements EntityInterface {
}
public static function create_hooks() {
add_action( static::get_table_name( false ) . "_remove_secret_action",
[ static::class, "remove_secret_action" ],
10,
2 );
}
/**
* Set class properties based on array values
*
......@@ -44,21 +61,30 @@ abstract class AbstractEntity implements EntityInterface {
public function set_fields( array $atts ) {
foreach ( $atts as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
try {
if ( property_exists( static::class, $property ) ) {
$this->$property = $value;
} else {
throw new EntityException( 'Property does not exist!', 0, $property, static::class );
}
} catch ( EntityException $e ) {
$logger = new LoggerService();
$logger->warning( 'Error setting property.', [ "message" => $e->getMessage() ] );
}
}
}
}
/**
* Returns the table name associated with Entity
*
* @param bool $prefix Whether to return the prefix or not
*
* @return string
* @since 2.0.0
*/
public static function get_table_name($prefix = true) {
public static function get_table_name( bool $prefix = true ) {
global $wpdb;
......@@ -66,6 +92,104 @@ abstract class AbstractEntity implements EntityInterface {
}
/**
* Set the donor's secret
*
* @param string $timeout
*
* @return string|false
* @since 2.0.0
*/
public function create_secret( $timeout = '+10 minutes' ) {
$logger = new LoggerService();
$table = static::get_table_name( false );
try {
// Schedule for secret to be removed after timeout
if ( class_exists( 'ActionScheduler' ) ) {
// Remove existing action if exists
as_unschedule_action( $table . "_remove_secret_action", [ $this->id ] );
$timestamp = strtotime( $timeout );
// Create new action to remove secret
as_schedule_single_action( $timestamp, $table . "_remove_secret_action", [ $this->id ] );
$logger->debug( sprintf( "Action %s_remove_secret_action scheduled", $table ),
[
'datetime' => wp_date( 'Y-m-d H:i:s', $timestamp ),
] );
}
// Create secret if none set
if ( null === $this->secret ) {
$this->secret = bin2hex( random_bytes( 10 ) );
}
return password_hash( $this->secret, PASSWORD_DEFAULT );
} catch ( Throwable $e ) {
$logger->error( sprintf( 'Unable to create secret for %s. ', $table ) . $e->getMessage(),
[ 'id' => $this->id ] );
return false;
}
}
/**
* Clears the Entities secret
*
* @since 2.0.0
*/
public function clear_secret() {
$this->secret = null;
}
/**
* Removes the secret for the current entity where
* it matches the provided id
*
* @param $id
*
* @return bool|int
*/
public static function remove_secret_action( $id ) {
if ( $id ) {
$mapper = new MapperService( static::class );
/** @var AbstractEntity $entity */
$entity = $mapper->get_one_by( [ 'id' => $id ] );
if ( ! $entity ) {
return false;
}
$entity->clear_secret();
return $mapper->save( $entity );
}
return false;
}
/**
* Verify donor's secret
*
* @param string $hash
*
* @return bool
* @since 2.0.0
*/
public function verify_secret( string $hash ) {
return password_verify( $this->secret, $hash );
}
/**
* Returns class as an array using type casting
*
......
......@@ -3,9 +3,7 @@
namespace Kudos\Entity;
use DateTime;
use Kudos\Service\LoggerService;
use Kudos\Service\MapperService;
use Throwable;
class DonorEntity extends AbstractEntity {
......@@ -46,14 +44,9 @@ class DonorEntity extends AbstractEntity {
* @var DateTime
*/
public $last_updated;
/**
* @var string
*/
public $secret;
/**
* Add donor_created
* DonorEntity constructor
*
* @param $atts
*
......@@ -71,76 +64,10 @@ class DonorEntity extends AbstractEntity {
* @return array|null
*/
public function get_transactions() {
$mapper = new MapperService( TransactionEntity::class );
return $mapper->get_all_by( [ 'customer_id' => $this->customer_id ] );
}
/**
* Set the donor's secret
*
* @param string $timeout
*
* @return string
* @since 2.0.0
*/
public function create_secret( $timeout = '+10 minutes' ) {
$logger = new LoggerService();
try {
// Schedule for secret to be removed after timeout
if ( class_exists( 'ActionScheduler' ) ) {
// Remove existing action if exists
as_unschedule_action( 'kudos_remove_secret_action', [ $this->customer_id ] );
$timestamp = strtotime( $timeout );
// Create new action to remove secret
as_schedule_single_action( $timestamp, 'kudos_remove_secret_action', [ $this->customer_id ] );
$logger->debug( 'Action "kudos_remove_secret_action" scheduled',
[
'datetime' => date_i18n( 'Y-m-d H:i:s', $timestamp ),
] );
}
// Create secret if none set
if ( null === $this->secret ) {
$this->secret = bin2hex( random_bytes( 10 ) );
}
} catch ( Throwable $e ) {
$logger->error( 'Unable to create secret for user. ' . $e->getMessage(), [ 'id' => $this->id ] );
}
return $this->secret;
}
/**
* Verify donor's secret
*
* @param string $hash
*
* @return bool
* @since 2.0.0
*/
public function verify_secret( string $hash ) {
return password_verify( $this->secret, $hash );
}
/**
* Clears the donor's secret
*
* @since 2.0.0
*/
public function clear_secret() {
$this->secret = '';
}
......
......@@ -93,29 +93,6 @@ class Front {
}
/**
* Remove secret key associated with donor
*
* @param string $customer_id
*
* @return bool|int
* @since 2.0.0
*/
public static function remove_donor_secret( string $customer_id ) {
if ( $customer_id ) {
$mapper = new MapperService( DonorEntity::class );
/** @var DonorEntity $donor */
$donor = $mapper->get_one_by( [ 'customer_id' => $customer_id ] );
$donor->clear_secret();
return $mapper->save( $donor );
}
return false;
}
/**
* Register the stylesheets for the public-facing side of the site.
*
......@@ -467,9 +444,8 @@ class Front {
}
$modal = new KudosModal();
$donor = $subscription->get_donor();
if ( $donor->verify_secret( $token ) ) {
if ( $subscription->verify_secret( $token ) ) {
$kudos_mollie = MollieService::factory();
if ( $kudos_mollie->cancel_subscription( $subscription_id ) ) {
echo $modal->get_message_modal( [
......
......@@ -3,6 +3,9 @@
namespace Kudos;
use Kudos\Admin\Admin;
use Kudos\Entity\DonorEntity;
use Kudos\Entity\SubscriptionEntity;
use Kudos\Entity\TransactionEntity;
use Kudos\Front\Front;
use Kudos\Helpers\Settings;
use Kudos\Service\ActivatorService;
......@@ -171,7 +174,11 @@ class KudosDonations {
$this->loader->add_action( 'wp_footer', $plugin_public, 'handle_query_variables', 1000 );
$this->loader->add_action( 'query_vars', $plugin_public, 'register_vars' );
$this->loader->add_action( 'kudos_process_paid_transaction', $plugin_public, 'process_transaction', 10, 1 );
$this->loader->add_action( 'kudos_remove_secret_action', $plugin_public, 'remove_donor_secret', 10, 1 );
// Entity Hooks
TransactionEntity::create_hooks();
DonorEntity::create_hooks();
SubscriptionEntity::create_hooks();
}
......
......@@ -113,6 +113,7 @@ class ActivatorService {
subscription_id VARCHAR(255),
refunds BLOB DEFAULT NULL,
campaign_label VARCHAR(255),
secret VARCHAR(255),
PRIMARY KEY (id)
) $charset_collate;";
......@@ -145,6 +146,7 @@ class ActivatorService {
transaction_id VARCHAR(255),
subscription_id VARCHAR(255),
status VARCHAR(255),
secret VARCHAR(255),
PRIMARY KEY (id)
) $charset_collate";
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment