Skip to content

Commit

Permalink
Merge pull request #88 from kidunot89/feature/ql-session-handler
Browse files Browse the repository at this point in the history
QL Session Handler
  • Loading branch information
kidunot89 authored Jun 23, 2019
2 parents 1b1bb68 + baac48a commit 0d6cf25
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
71 changes: 71 additions & 0 deletions includes/class-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use WPGraphQL\Extensions\WooCommerce\Data\Factory;
use WPGraphQL\Extensions\WooCommerce\Data\Loader\WC_Customer_Loader;
use WPGraphQL\Extensions\WooCommerce\Data\Loader\WC_Post_Crud_Loader;
use WPGraphQL\Extensions\WooCommerce\Utils\QL_Session_Handler;

/**
* Class Filters
Expand All @@ -34,12 +35,24 @@ class Filters {
*/
private static $post_crud_loader;

/**
* Stores instance session header name.
*
* @var string
*/
private static $session_header;

/**
* Register filters
*/
public static function load() {
// Registers WooCommerce taxonomies.
add_filter( 'register_taxonomy_args', array( __CLASS__, 'register_taxonomy_args' ), 10, 2 );

// Add data-loaders to AppContext.
add_filter( 'graphql_data_loaders', array( __CLASS__, 'graphql_data_loaders' ), 10, 2 );

// Filter core connection resolutions.
add_filter(
'graphql_post_object_connection_query_args',
array( __CLASS__, 'graphql_post_object_connection_query_args' ),
Expand All @@ -52,6 +65,13 @@ public static function load() {
10,
5
);

// Setup QL session handler.
self::$session_header = apply_filters( 'woocommerce_session_header_name', 'woocommerce-session' );
add_filter( 'woocommerce_cookie', array( __CLASS__, 'woocommerce_cookie' ) );
add_filter( 'woocommerce_session_handler', array( __CLASS__, 'init_ql_session_handler' ) );
add_filter( 'graphql_response_headers_to_send', array( __CLASS__, 'add_session_header_to_expose_headers' ) );
add_filter( 'graphql_access_control_allow_headers', array( __CLASS__, 'add_session_header_to_allow_headers' ) );
}

/**
Expand Down Expand Up @@ -182,4 +202,55 @@ public static function graphql_post_object_connection_query_args( $query_args, $
public static function graphql_term_object_connection_query_args( $query_args, $source, $args, $context, $info ) {
return WC_Terms_Connection_Resolver::get_query_args( $query_args, $source, $args, $context, $info );
}

/**
* Filters WooCommerce cookie key to be used as a HTTP Header on GraphQL HTTP requests
*
* @param string $cookie WooCommerce cookie key.
*
* @return string
*/
public static function woocommerce_cookie( $cookie ) {
return self::$session_header;
}

/**
* Filters WooCommerce session handler class on GraphQL HTTP requests
*
* @param string $session_class Classname of the current session handler class.
*
* @return string
*/
public static function init_ql_session_handler( $session_class ) {
return QL_Session_Handler::class;
}

/**
* Append session header to the exposed headers in GraphQL responses
*
* @param array $headers GraphQL responser headers.
*
* @return array
*/
public static function add_session_header_to_expose_headers( $headers ) {
if ( empty( $headers['Access-Control-Expose-Headers'] ) ) {
$headers['Access-Control-Expose-Headers'] = apply_filters( 'woocommerce_cookie', self::$session_header );
} else {
$headers['Access-Control-Expose-Headers'] .= ', ' . apply_filters( 'woocommerce_cookie', self::$session_header );
}

return $headers;
}

/**
* Append the session header to the allowed headers in GraphQL responses
*
* @param array $allowed_headers The existing allowed headers.
*
* @return array
*/
public static function add_session_header_to_allow_headers( array $allowed_headers ) {
$allowed_headers[] = self::$session_header;
return $allowed_headers;
}
}
137 changes: 137 additions & 0 deletions includes/utils/class-ql-session-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php
/**
* Handles data for the current customers session.
*
* @package WPGraphQL\Extensions\WooCommerce\Utils
* @since 0.1.2
*/

namespace WPGraphQL\Extensions\WooCommerce\Utils;

/**
* Class - QL_Session_Handler
*/
class QL_Session_Handler extends \WC_Session_Handler {
/**
* Encrypt and decrypt
*
* @author Nazmul Ahsan <n.mukto@gmail.com>
* @author Geoff Taylor <kidunot89@gmail.com>
* @link http://nazmulahsan.me/simple-two-way-function-encrypt-decrypt-string/
*
* @param string $string string to be encrypted/decrypted.
* @param string $action what to do with this? e for encrypt, d for decrypt.
*
* @return string
*/
private function crypt( $string, $action = 'e' ) {
// you may change these values to your own.
$secret_key = apply_filters( 'woographql_session_header_secret_key', 'my_simple_secret_key' );
$secret_iv = apply_filters( 'woographql_session_header_secret_iv', 'my_simple_secret_iv' );

$output = false;
$encrypt_method = 'AES-256-CBC';
$key = hash( 'sha256', $secret_key );
$iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );

if ( 'e' === $action ) {
$output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
} elseif ( 'd' === $action ) {
$output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
}

return $output;
}

/**
* Returns formatted $_SERVER index from provided string.
*
* @param string $string String to be formatted.
*
* @return string
*/
private function get_server_key( $string ) {
return 'HTTP_' . strtoupper( preg_replace( '#[^A-z0-9]#', '_', $string ) );
}

/**
* Encrypts and sets the session header on-demand (usually after adding an item to the cart).
*
* Warning: Headers will only be set if this is called before the headers are sent.
*
* @param bool $set Should the session cookie be set.
*/
public function set_customer_session_cookie( $set ) {
if ( $set ) {
$to_hash = $this->_customer_id . '|' . $this->_session_expiration;
$cookie_hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
$cookie_value = $this->_customer_id . '||' . $this->_session_expiration . '||' . $this->_session_expiring . '||' . $cookie_hash;
$this->_has_cookie = true;
if ( ! isset( $_SERVER[ $this->_cookie ] ) || $_SERVER[ $this->_cookie ] !== $cookie_value ) {
add_filter(
'graphql_response_headers_to_send',
function( $headers ) use ( $cookie_value ) {
$headers[ $this->_cookie ] = $this->crypt( $cookie_value, 'e' );
return $headers;
}
);
}
}
}

/**
* Return true if the current user has an active session, i.e. a cookie to retrieve values.
*
* @return bool
*/
public function has_session() {
// @codingStandardsIgnoreLine.
return isset( $_SERVER[ $this->get_server_key( $this->_cookie ) ] ) || $this->_has_cookie || is_user_logged_in();
}

/**
* Retrieve and decrypt the session data from session, if set. Otherwise return false.
*
* Session cookies without a customer ID are invalid.
*
* @return bool|array
*/
public function get_session_cookie() {
// @codingStandardsIgnoreStart.
$cookie_value = isset( $_SERVER[ $this->get_server_key( $this->_cookie ) ] )
? $this->crypt( $_SERVER[ $this->get_server_key( $this->_cookie ) ], 'd' )
: false;
// @codingStandardsIgnoreEnd.
if ( empty( $cookie_value ) || ! is_string( $cookie_value ) ) {
return false;
}
list( $customer_id, $session_expiration, $session_expiring, $cookie_hash ) = explode( '||', $cookie_value );
if ( empty( $customer_id ) ) {
return false;
}
// Validate hash.
$to_hash = $customer_id . '|' . $session_expiration;
$hash = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) {
return false;
}
return array( $customer_id, $session_expiration, $session_expiring, $cookie_hash );
}

/**
* Forget all session data without destroying it.
*/
public function forget_session() {
add_filter(
'graphql_response_headers_to_send',
function( $headers ) {
$headers[ $this->_cookie ] = 'false';
return $headers;
}
);
wc_empty_cart();
$this->_data = array();
$this->_dirty = false;
$this->_customer_id = $this->generate_customer_id();
}
}
1 change: 1 addition & 0 deletions vendor/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Shipping_Method_Type' => $baseDir . '/includes/type/object/class-shipping-method-type.php',
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Tax_Rate_Type' => $baseDir . '/includes/type/object/class-tax-rate-type.php',
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Variation_Attribute_Type' => $baseDir . '/includes/type/object/class-variation-attribute-type.php',
'WPGraphQL\\Extensions\\WooCommerce\\Utils\\QL_Session_Handler' => $baseDir . '/includes/utils/class-ql-session-handler.php',
'WP_GraphQL_WooCommerce' => $baseDir . '/includes/class-wp-graphql-woocommerce.php',
);
1 change: 1 addition & 0 deletions vendor/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class ComposerStaticInitee0d17af17b841ed3a93c4a0e5cc5e5f
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Shipping_Method_Type' => __DIR__ . '/../..' . '/includes/type/object/class-shipping-method-type.php',
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Tax_Rate_Type' => __DIR__ . '/../..' . '/includes/type/object/class-tax-rate-type.php',
'WPGraphQL\\Extensions\\WooCommerce\\Type\\WPObject\\Variation_Attribute_Type' => __DIR__ . '/../..' . '/includes/type/object/class-variation-attribute-type.php',
'WPGraphQL\\Extensions\\WooCommerce\\Utils\\QL_Session_Handler' => __DIR__ . '/../..' . '/includes/utils/class-ql-session-handler.php',
'WP_GraphQL_WooCommerce' => __DIR__ . '/../..' . '/includes/class-wp-graphql-woocommerce.php',
);

Expand Down

0 comments on commit 0d6cf25

Please sign in to comment.