Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QL Session Handler #88

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 71 additions & 0 deletions includes/class-filters.php
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
@@ -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
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
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