mirror of
https://github.com/wp-graphql/wp-graphql-woocommerce.git
synced 2026-08-14 12:53:44 +02:00
feat: Authorizing URLs implemented and tested. (#745)
* feat: Authorizing URLs implemented and tested. * feat: More woographql_*_nonce functions implemented. * chore: linting changes made. * chore: linting changes made. * fix: woographql_*_ functions tested. * chore: WPCS compliance met. * devops: lint-code script updated to PHP v8.0 * chore: WPCS compliance met * devops: TransferSessionHandlerTest & QLSessionHandlerTest updated * devops: codeclimate.yml added. * chore: Linter compliance met * devops: Harmonizing WordPress doc written and Settings doc updated. * chore: Typo fixed in docs. * fix: General bugfixes and improvements related to Auth URLs * devops: More docs. * chore: Linter compliance met * chore: small change made to docs.
This commit is contained in:
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.3
|
||||
php-version: 8.0
|
||||
extensions: mbstring, intl
|
||||
tools: composer
|
||||
|
||||
|
||||
+86
-6
@@ -229,12 +229,6 @@ if ( ! function_exists( 'wc_graphql_camel_case_to_underscore' ) ) {
|
||||
}
|
||||
}//end if
|
||||
|
||||
/**
|
||||
* Plugin global functions.
|
||||
*
|
||||
* @package Axis\Plugin_Distributor
|
||||
*/
|
||||
|
||||
if ( ! function_exists( 'woographql_setting' ) ) :
|
||||
/**
|
||||
* Get an option value from WooGraphQL settings
|
||||
@@ -275,6 +269,92 @@ if ( ! function_exists( 'woographql_setting' ) ) :
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'woographql_get_session_uid' ) ) :
|
||||
/**
|
||||
* Returns end-user's customer ID.
|
||||
*
|
||||
* @return string|int
|
||||
*/
|
||||
function woographql_get_session_uid() {
|
||||
return WC()->session->get_customer_id();
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'woographql_get_session_token' ) ) :
|
||||
/**
|
||||
* Returns session user's "client_session_id"
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function woographql_get_session_token() {
|
||||
return WC()->session->get_client_session_id();
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'woographql_create_nonce' ) ) :
|
||||
/**
|
||||
* Creates WooGraphQL session transfer nonces.
|
||||
*
|
||||
* @param string $action Nonce name.
|
||||
*/
|
||||
function woographql_create_nonce( $action = -1 ) {
|
||||
$uid = woographql_get_session_uid();
|
||||
$token = woographql_get_session_token();
|
||||
$i = wp_nonce_tick( $action );
|
||||
|
||||
return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'woographql_verify_nonce' ) ) :
|
||||
/**
|
||||
* Validate WooGraphQL session transfer nonces.
|
||||
*
|
||||
* @param string $nonce Nonce to validated.
|
||||
* @param integer|string $action Nonce name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function woographql_verify_nonce( $nonce, $action = -1 ) {
|
||||
$nonce = (string) $nonce;
|
||||
$uid = woographql_get_session_uid();
|
||||
|
||||
if ( empty( $nonce ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = woographql_get_session_token();
|
||||
$i = wp_nonce_tick( $action );
|
||||
|
||||
// Nonce generated 0-12 hours ago.
|
||||
$expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
|
||||
if ( hash_equals( $expected, $nonce ) ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Nonce generated 12-24 hours ago.
|
||||
$expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
|
||||
if ( hash_equals( $expected, $nonce ) ) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when nonce verification fails.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param string $nonce The invalid nonce.
|
||||
* @param string|int $action The nonce action.
|
||||
* @param WP_User $user The current user object.
|
||||
* @param string $token The user's session token.
|
||||
*/
|
||||
do_action( 'graphql_verify_nonce_failed', $nonce, $action, $uid, $token );
|
||||
|
||||
// Invalid nonce.
|
||||
return false;
|
||||
}
|
||||
endif;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ modules:
|
||||
REST:
|
||||
depends: WPBrowser
|
||||
url: '%WORDPRESS_URL%'
|
||||
cookies: false
|
||||
WPFilesystem:
|
||||
wpRootFolder: '%WP_CORE_DIR%'
|
||||
plugins: '/wp-content/plugins'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
plugins:
|
||||
phpcodesniffer:
|
||||
enabled: true
|
||||
config:
|
||||
standard: "phpcs.xml.dist"
|
||||
@@ -25,6 +25,7 @@
|
||||
"firebase/php-jwt": "^6.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/vipwpcs": "^2.3",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"wp-coding-standards/wpcs": "^2.3"
|
||||
},
|
||||
@@ -34,6 +35,7 @@
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"johnpbloch/wordpress-core-installer": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"composer/installers": true
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+204
-18
@@ -4,29 +4,29 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "49e1ecf4a41487b3de0547b8e3860c6a",
|
||||
"content-hash": "57cb19ef33e8b4bd0343bb725e4c0f6b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v6.4.0",
|
||||
"version": "v6.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/firebase/php-jwt.git",
|
||||
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224"
|
||||
"reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||
"reference": "4dd1e007f22a927ac77da5a3fbb067b42d3bc224",
|
||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2",
|
||||
"reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1||^8.0"
|
||||
"php": "^7.4||^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^6.5||^7.4",
|
||||
"phpspec/prophecy-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "^7.5||^9.5",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^1.0||^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
@@ -65,24 +65,209 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v6.4.0"
|
||||
"source": "https://github.com/firebase/php-jwt/tree/v6.5.0"
|
||||
},
|
||||
"time": "2023-02-09T21:01:23+00:00"
|
||||
"time": "2023-05-12T15:47:07+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.7.1",
|
||||
"name": "automattic/vipwpcs",
|
||||
"version": "2.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
|
||||
"url": "https://github.com/Automattic/VIP-Coding-Standards.git",
|
||||
"reference": "6cd0a6a82bc0ac988dbf9d6a7c2e293dc8ac640b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||
"url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/6cd0a6a82bc0ac988dbf9d6a7c2e293dc8ac640b",
|
||||
"reference": "6cd0a6a82bc0ac988dbf9d6a7c2e293dc8ac640b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7",
|
||||
"php": ">=5.4",
|
||||
"sirbrillig/phpcs-variable-analysis": "^2.11.1",
|
||||
"squizlabs/php_codesniffer": "^3.5.5",
|
||||
"wp-coding-standards/wpcs": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-console-highlighter": "^0.5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.0",
|
||||
"phpcompatibility/php-compatibility": "^9",
|
||||
"phpcsstandards/phpcsdevtools": "^1.0",
|
||||
"phpunit/phpunit": "^4 || ^5 || ^6 || ^7"
|
||||
},
|
||||
"type": "phpcodesniffer-standard",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Contributors",
|
||||
"homepage": "https://github.com/Automattic/VIP-Coding-Standards/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress VIP minimum coding conventions",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
"standards",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Automattic/VIP-Coding-Standards/issues",
|
||||
"source": "https://github.com/Automattic/VIP-Coding-Standards",
|
||||
"wiki": "https://github.com/Automattic/VIP-Coding-Standards/wiki"
|
||||
},
|
||||
"time": "2021-09-29T16:20:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dealerdirect/phpcodesniffer-composer-installer",
|
||||
"version": "v0.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
|
||||
"reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db",
|
||||
"reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^1.0 || ^2.0",
|
||||
"php": ">=5.3",
|
||||
"squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "*",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3.1",
|
||||
"phpcompatibility/php-compatibility": "^9.0"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Franck Nijhof",
|
||||
"email": "franck.nijhof@dealerdirect.com",
|
||||
"homepage": "http://www.frenck.nl",
|
||||
"role": "Developer / IT Manager"
|
||||
},
|
||||
{
|
||||
"name": "Contributors",
|
||||
"homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
|
||||
"homepage": "http://www.dealerdirect.com",
|
||||
"keywords": [
|
||||
"PHPCodeSniffer",
|
||||
"PHP_CodeSniffer",
|
||||
"code quality",
|
||||
"codesniffer",
|
||||
"composer",
|
||||
"installer",
|
||||
"phpcbf",
|
||||
"phpcs",
|
||||
"plugin",
|
||||
"qa",
|
||||
"quality",
|
||||
"standard",
|
||||
"standards",
|
||||
"style guide",
|
||||
"stylecheck",
|
||||
"tests"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
|
||||
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
|
||||
},
|
||||
"time": "2022-02-04T12:51:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sirbrillig/phpcs-variable-analysis",
|
||||
"version": "v2.11.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sirbrillig/phpcs-variable-analysis.git",
|
||||
"reference": "dc5582dc5a93a235557af73e523c389aac9a8e88"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/dc5582dc5a93a235557af73e523c389aac9a8e88",
|
||||
"reference": "dc5582dc5a93a235557af73e523c389aac9a8e88",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"squizlabs/php_codesniffer": "^3.5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
|
||||
"phpcsstandards/phpcsdevcs": "^1.1",
|
||||
"phpstan/phpstan": "^1.7",
|
||||
"phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0",
|
||||
"sirbrillig/phpcs-import-detection": "^1.1",
|
||||
"vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0@beta"
|
||||
},
|
||||
"type": "phpcodesniffer-standard",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VariableAnalysis\\": "VariableAnalysis/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sam Graham",
|
||||
"email": "php-codesniffer-variableanalysis@illusori.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Payton Swick",
|
||||
"email": "payton@foolord.com"
|
||||
}
|
||||
],
|
||||
"description": "A PHPCS sniff to detect problems with variables.",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues",
|
||||
"source": "https://github.com/sirbrillig/phpcs-variable-analysis",
|
||||
"wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki"
|
||||
},
|
||||
"time": "2023-03-31T16:46:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879",
|
||||
"reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -118,14 +303,15 @@
|
||||
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
"standards"
|
||||
"standards",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||
},
|
||||
"time": "2022-06-18T07:21:10+00:00"
|
||||
"time": "2023-02-22T23:07:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "wp-coding-standards/wpcs",
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
---
|
||||
title: "Harmonizing with WordPress"
|
||||
description: "Learn how to create a secure checkout button and understand the security behind the URL validation."
|
||||
keywords: "WooGraphQL, WooCommerce, checkout functionality, secure checkout, session transferring URLs, nonce generation, session meta, URL validation, Client Session ID, UpdateSession mutation, GraphQL, cart page, WordPress, frontend security"
|
||||
author: "Geoff Taylor"
|
||||
---
|
||||
|
||||
# Harmonizing with WordPress
|
||||
|
||||
In our [previous guide](using-cart-data.md), we created a cart page for our WooCommerce store. Now, we are going to add a "Checkout" button to this page and discuss how to ensure the security of the process.
|
||||
|
||||
Our checkout button's `href` attribute will be a nonced URL that directs the user to a particular endpoint on the WP backend. The endpoint performs a set of operations such as validating the URL, loading the WooCommerce session, authenticating the registered user, and redirecting to the checkout page. If the URL validation fails, the user will be redirected to the WP homepage.
|
||||
|
||||
## Enabling Dedicated Router and Nonce Generation
|
||||
|
||||
Before starting with the checkout button creation, we need to activate the dedicated router and nonce generation feature introduced in WooGraphQL v0.13.0. To do this, enable the "User Session transferring URLs" option in your WooGraphQL settings. Also, make sure to check the "Checkout URL" checkbox under this option.
|
||||
|
||||

|
||||
|
||||
Although it's not mandatory, it's recommended to set the "Endpoint for Authorizing URLs" and "Checkout URL nonce name" settings with secure values for increased security.
|
||||
|
||||

|
||||
|
||||
## Creating the Checkout Button
|
||||
|
||||
To create a checkout button in our CartPage component, we can use the `checkoutUrl` field from the `customer` query. This URL is dynamically generated by our GraphQL server and is now ready for use. Let's update our CartPage component:
|
||||
|
||||
```jsx
|
||||
// ...existing code...
|
||||
<a className="button" href={customer.checkoutUrl}>Checkout</a>
|
||||
// ...existing code...
|
||||
```
|
||||
|
||||
## Understanding URL Validation and Security
|
||||
|
||||
Although our checkout button is now functional, it's important to note that the default URL validation process can be improved in terms of security. In the next part of this guide, we will discuss the security measures and how to enhance them.
|
||||
|
||||
### Improving Security with Client Session ID
|
||||
|
||||
The first step is to create a "Client Session ID" in our app and pass it to the WooCommerce session using the `updateSession` mutation. This mutation allows us to set session metadata directly in the WooCommerce session data object. The `client_session_id` meta is used by WooGraphQL to create the nonce.
|
||||
|
||||
If WooGraphQL cannot find this meta, it generates one on the server, making the URL potentially usable on any machine. This is why it's important to avoid an arbitrary string as the session ID. Instead, we should use a value tied to the user's machine, like their IP or User Agent. Afterward, this value should be one-way hashed for increased security.
|
||||
|
||||
Furthermore, we also need to set the `client_session_id_expiration` as a string value representing the expiration time in seconds. For example, to set an expiration time of one hour from now, we could use: `Math.floor(new Date().getTime() / 1000) + 3600`.
|
||||
|
||||
We can achieve all of this by using the `updateSession` mutation as follows:
|
||||
|
||||
```graphql
|
||||
mutation($input: UpdateSessionInput!) {
|
||||
updateSession(input: $input) {
|
||||
customer {
|
||||
checkoutUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With the input fields populated with secure values:
|
||||
|
||||
```js
|
||||
const input = {
|
||||
sessionData: [
|
||||
{
|
||||
key: 'client_session_id',
|
||||
value: 'secure_hashed_value', // Replace this with your secure hashed value.
|
||||
},
|
||||
{
|
||||
key: 'client_session_id_expiration',
|
||||
value: `${Math.floor(new Date().getTime() / 1000) + 3600}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When the `client_session_id` or `client_session_id_expiration` values become invalid or expired, WooGraphQL generates new values with an expiration time of one hour. To avoid this, we recommend you to periodically update these values from the client side and retrieve a new `checkoutUrl` each time.
|
||||
|
||||
## Reinventing Security: The Client-Side Nonce
|
||||
|
||||
Next we're going to explore an advanced approach to enhance the security of our checkout procedure by generating a nonce on the client side. By doing this, and not pulling the Nonces or Auth URLs from WooGraphQL we remove any risk of leakage thru GraphQL request and further protect the end-user's data and the WordPress backend. This process will involve recreating some of PHP and WordPress core functions in JavaScript.
|
||||
|
||||
1. **PHP `time` Function in JavaScript**
|
||||
|
||||
Our first stop is to rewrite the PHP `time` function in JavaScript, which returns the current Unix timestamp. Here's how we can do it:
|
||||
|
||||
```js
|
||||
function time() {
|
||||
return Math.floor(new Date().getTime() / 1000);
|
||||
}
|
||||
```
|
||||
|
||||
2. **WordPress `wp_nonce_tick` Function in JavaScript**
|
||||
|
||||
Next, we translate the WordPress function `wp_nonce_tick` to JavaScript. This function returns a time-dependent variable for nonce creation:
|
||||
|
||||
```js
|
||||
const MINUTE_IN_SECONDS = 60;
|
||||
const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS;
|
||||
const DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS;
|
||||
|
||||
function nonceTick() {
|
||||
const nonceLife = DAY_IN_SECONDS;
|
||||
return Math.ceil(time() / (nonceLife / 2));
|
||||
}
|
||||
```
|
||||
|
||||
3. **WordPress `wp_hash` Function in JavaScript**
|
||||
|
||||
The `wp_hash` function, another WordPress core function, will be adapted to JavaScript as well. This function uses the `wp_salt` function to retrieve the salt from WordPress Salt constants, usually defined in the `wp-config.php` file. In our context, we only need the `nonce` salt. Therefore, it's crucial to ensure the `NONCE_KEY` and `NONCE_SALT` constants are set on the WordPress installation, and their values are accessible in our front-end application.
|
||||
|
||||
`wp_hash` also uses `hash_hmac` and `md5` encryption to create the hash. For this, we'll utilize `crypto-js`, which you can install with `npm` using the command `npm install crypto-js`. Here's how to write `wp_hash` in JavaScript:
|
||||
|
||||
```js
|
||||
import { HmacMD5 } from 'crypto-js';
|
||||
|
||||
export function wpNonceHash(data) {
|
||||
const nonceSalt = process.env.NONCE_KEY + process.env.NONCE_SALT;
|
||||
const hash = HmacMD5(data, nonceSalt).toString();
|
||||
|
||||
return hash;
|
||||
}
|
||||
```
|
||||
|
||||
With these functions ready, we can essentially recreate the `woographql_create_nonce` PHP function employed by WooGraphQL to create the nonce. Below is the JavaScript version:
|
||||
|
||||
```js
|
||||
export function createNonce(action, uId, token) {
|
||||
const i = nonceTick();
|
||||
|
||||
const nonce = wpNonceHash(`${i}|${action}|${uId}|${token}`).slice(-12, -2);
|
||||
|
||||
return nonce;
|
||||
}
|
||||
```
|
||||
|
||||
In the function above:
|
||||
- The `action` parameter represents the nonce action name.
|
||||
- The `uId` parameter represents the end-user's session ID, either their WP User Database ID (if they are authenticated) or a random string (if they are a guest). To retrieve this value, we'll have to decode the WooCommerce Session Token used by ApolloClient.
|
||||
- The `token` is our Client Session ID mentioned earlier.
|
||||
|
||||
4. **Generating the URLs**
|
||||
|
||||
Having the nonce alone is not enough, so let's move on to generating our URLs. The process involves three functions and the `jwt-decode` library. Install it using npm with the command `npm install jwt-decode`.
|
||||
|
||||
Here's the JavaScript code to generate the URL:
|
||||
|
||||
```js
|
||||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
function getAction(action, uId) {
|
||||
switch (action) {
|
||||
case 'cart':
|
||||
return `load-cart_${uId}`;
|
||||
case 'checkout':
|
||||
return `load-checkout_${uId}`;
|
||||
case 'new-payment':
|
||||
return `load-account_${uId}`;
|
||||
case 'change-sub':
|
||||
return `change-sub_${uId}`;
|
||||
case 'renew-sub':
|
||||
return `renew-sub_${uId}`;
|
||||
default:
|
||||
throw new Error('Invalid nonce action provided.');
|
||||
}
|
||||
}
|
||||
|
||||
function getNonceParam(action) {
|
||||
switch (action) {
|
||||
case 'cart':
|
||||
return '_wc_cart';
|
||||
case 'checkout':
|
||||
return '_wc_checkout';
|
||||
case 'new-payment':
|
||||
return '_wc_payment';
|
||||
case 'change-sub':
|
||||
return '_wc_change_sub';
|
||||
case 'renew-sub':
|
||||
return '_wc_renew_sub';
|
||||
default:
|
||||
throw new Error('Invalid nonce action provided.');
|
||||
}
|
||||
}
|
||||
|
||||
export function generateUrl(sessionToken, clientSessionId, actionType) {
|
||||
const decodedToken = jwtDecode(sessionToken);
|
||||
if (!decodedToken?.data?.customer_id) {
|
||||
throw new Error('Failed to decode session token');
|
||||
}
|
||||
const uId = decodedToken.data.customer_id;
|
||||
const action = getAction(actionType, uId);
|
||||
|
||||
// Create nonce
|
||||
const nonce = createNonce(action, uId, clientSessionId);
|
||||
|
||||
// Create URL.
|
||||
const param = getNonceParam(actionType);
|
||||
let url = `${process.env.WORDPRESS_URL}/transfer-session?session_id=${uId}&${param}=${nonce}`;
|
||||
|
||||
// Add subscription ID placeholder if subscription action.
|
||||
if (actionType === 'change-sub' || actionType === 'renew-sub') {
|
||||
url = `${url}&sub=%SUBSCRIPTION_ID%`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
```
|
||||
|
||||
Note that for the `change-sub` and `renew-sub` actions from WooGraphQL Pro, we are passing a `%SUBSCRIPTION_ID%` placeholder to be replaced with a subscription database ID before use.
|
||||
|
||||
To get our checkout URL, you would run:
|
||||
|
||||
```js
|
||||
const checkoutUrl = generateUrl(sessionToken, clientSessionId, 'checkout');
|
||||
```
|
||||
|
||||
Also, `transfer-session` is the default name of the authorization endpoint. This can be altered in the WooGraphQL settings on the WP Dashboard.
|
||||
|
||||
To confirm the validity of your URL, compare it with the Auth URLs generated by WooGraphQL with the same `client_session_id` and ensure they are identical.
|
||||
|
||||
## Conclusion
|
||||
|
||||
With this guide, you should now be able to add a secure checkout button to your WooCommerce cart page using WooGraphQL. Keep in mind that even though we've improved security by setting a client-side session ID and expiration, these measures should be part of a broader security strategy. Always ensure to follow best practices to keep your application and user data safe.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 102 KiB |
@@ -25,6 +25,21 @@ The default WooCommerce User Session Handler is responsible for capturing cart a
|
||||
|
||||
The settings is simple to understand and likely to be enabled if your using a WC extension that uses a product type that isn't support by WooGraphQL out-of-box. When enabled it will substitute the missing type with the SimpleProduct type. This way you can still you the product type and possibly pull what extra data you need from the `Product`'s `metaData` field.
|
||||
|
||||

|
||||
|
||||
## Enable User Session transferring URLs
|
||||
|
||||
This settings when enabled activated the WooCommerce Session-backed nonce generator and transfer session endpoint for passing a user's session from a client to the WordPress installation, the primary use of these nonces are to create authorizing URLs that enable the user to travel to the backend as if it were a part of the front-end application. This setting is disabled if the QL Session Handler is disabled as it required for nonce generation to work. The next four settings are all about customizing the names of different parts of the authorizing URL..
|
||||
|
||||
### Endpoint for Authorizing URLs
|
||||
|
||||
The endpoint (path) for transferring user sessions on the site. Defaults to `transfer-session`.
|
||||
|
||||
### Cart URL nonce name, Checkout URL nonce name, and Add Payment Method URL nonce name
|
||||
|
||||
The name of the nonce param for each respective URLs. They have to be unique and cannot be identical.
|
||||
|
||||
Uses these settings alone is very insecure. It's highly recommended that specific measures be taken on the client to further secure the WP backend and end-user's data.
|
||||
## WooGraphQL Pro Settings
|
||||
|
||||
These settings allow you to enable or disable the GraphQL schema types, queries, and mutations for various WooCommerce extensions supported by WooGraphQL Pro. This is useful if you have one of the supported extensions installed and activated but don't need it exposed to the GraphQL API, keeping the schema lightweight.
|
||||
|
||||
@@ -12,19 +12,56 @@ namespace WPGraphQL\WooCommerce\Admin;
|
||||
*/
|
||||
class General extends Section {
|
||||
|
||||
/**
|
||||
* Returns the other nonce values besides the one provided.
|
||||
*
|
||||
* @param string $excluded Slug of nonce value to be excluded.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_other_nonce_values( $excluded ) {
|
||||
$nonce_values = apply_filters(
|
||||
'woographql_authorizing_url_nonce_values',
|
||||
[
|
||||
'cart_url' => woographql_setting( 'cart_url_nonce_param', '_wc_cart' ),
|
||||
'checkout_url' => woographql_setting( 'checkout_url_nonce_param', '_wc_checkout' ),
|
||||
'add_payment_method_url' => woographql_setting( 'add_payment_method_url_nonce_param', '_wc_payment' ),
|
||||
]
|
||||
);
|
||||
|
||||
return array_values( array_diff_key( $nonce_values, [ $excluded => '' ] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns General settings fields.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_fields() {
|
||||
$custom_endpoint = apply_filters( 'woographql_authorizing_url_endpoint', null );
|
||||
$enabled_authorizing_url_fields = array_keys( woographql_setting( 'enable_authorizing_url_fields', [] ) );
|
||||
$all_urls_checked = apply_filters(
|
||||
'woographql_enabled_authorizing_url_fields',
|
||||
[
|
||||
'cart_url' => 'cart_url',
|
||||
'checkout_url' => 'checkout_url',
|
||||
'add_payment_method_url' => 'add_payment_method_url',
|
||||
]
|
||||
);
|
||||
|
||||
$cart_url_hardcoded = defined( 'CART_URL_NONCE_PARAM' ) && ! empty( CART_URL_NONCE_PARAM );
|
||||
$checkout_url_hardcoded = defined( 'CHECKOUT_URL_NONCE_PARAM' ) && ! empty( CHECKOUT_URL_NONCE_PARAM );
|
||||
$add_payment_method_url_hardcoded = defined( 'ADD_PAYMENT_METHOD_URL_NONCE_PARAM' ) && ! empty( ADD_PAYMENT_METHOD_URL_NONCE_PARAM );
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => 'disable_ql_session_handler',
|
||||
'label' => __( 'Disable QL Session Handler', 'wp-graphql-woocommerce' ),
|
||||
'desc' => __( 'The QL Session Handler takes over management of WooCommerce Session Management on WPGraphQL request replacing the usage of HTTP Cookies with JSON Web Tokens.', 'wp-graphql-woocommerce' ),
|
||||
'type' => 'checkbox',
|
||||
'default' => 'off',
|
||||
'name' => 'disable_ql_session_handler',
|
||||
'label' => __( 'Disable QL Session Handler', 'wp-graphql-woocommerce' ),
|
||||
'desc' => __( 'The QL Session Handler takes over management of WooCommerce Session Management on WPGraphQL request replacing the usage of HTTP Cookies with JSON Web Tokens.', 'wp-graphql-woocommerce' )
|
||||
. ( defined( 'NO_QL_SESSION_HANDLER' ) ? __( ' This setting is disabled. The "NO_QL_SESSION_HANDLER" flag has been triggered with code', 'wp-graphql-woocommerce' ) : '' ),
|
||||
'type' => 'checkbox',
|
||||
'value' => defined( 'NO_QL_SESSION_HANDLER' ) ? 'on' : woographql_setting( 'disable_ql_session_handler', 'off' ),
|
||||
'disabled' => defined( 'NO_QL_SESSION_HANDLER' ) ? true : false,
|
||||
],
|
||||
[
|
||||
'name' => 'enable_unsupported_product_type',
|
||||
@@ -33,6 +70,108 @@ class General extends Section {
|
||||
'type' => 'checkbox',
|
||||
'default' => 'off',
|
||||
],
|
||||
[
|
||||
'name' => 'enable_authorizing_url_fields',
|
||||
'label' => __( 'Enable User Session transferring URLs', 'wp-graphql-woocommerce' ),
|
||||
'desc' => __( 'URL fields to add to the <strong>Customer</strong> type.', 'wp-graphql-woocommerce' )
|
||||
. ( defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' ) ? __( ' This setting is disabled. The "WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS" flag has been triggered with code', 'wp-graphql-woocommerce' ) : '' ),
|
||||
'type' => 'multicheck',
|
||||
'options' => apply_filters(
|
||||
'woographql_settings_enable_authorizing_url_options',
|
||||
[
|
||||
'cart_url' => __( 'Cart URL. Field name: <strong>cartUrl</strong>', 'wp-graphql-woocommerce' ),
|
||||
'checkout_url' => __( 'Checkout URL. Field name: <strong>checkoutUrl</strong>', 'wp-graphql-woocommerce' ),
|
||||
'add_payment_method_url' => __( 'Add Payment Method URL. Field name: <strong>addPaymentMethodUrl</strong>', 'wp-graphql-woocommerce' ),
|
||||
]
|
||||
),
|
||||
'value' => defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' ) ? $all_urls_checked : woographql_setting( 'enable_authorizing_url_fields', [] ),
|
||||
'disabled' => defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' ),
|
||||
],
|
||||
[
|
||||
'name' => 'authorizing_url_endpoint',
|
||||
'label' => __( 'Endpoint for Authorizing URLs', 'wp-graphql-woocommerce' ),
|
||||
'desc' => sprintf(
|
||||
/* translators: %1$s: Site URL, %2$s: WooGraphQL Auth Endpoint */
|
||||
__( 'The endpoint (path) for transferring user sessions on the site. <a target="_blank" href="%1$s/%2$s">%1$s/%2$s</a>.', 'wp-graphql-woocommerce' ),
|
||||
site_url(),
|
||||
woographql_setting( 'authorizing_url_endpoint', 'transfer-session' )
|
||||
),
|
||||
'type' => 'text',
|
||||
'default' => ! empty( $custom_endpoint ) ? $custom_endpoint : 'transfer-session',
|
||||
'disabled' => empty( $enabled_authorizing_url_fields ),
|
||||
],
|
||||
[
|
||||
'name' => 'cart_url_nonce_param',
|
||||
'label' => __( 'Cart URL nonce name', 'wp-graphql-woocommerce' ),
|
||||
'desc' => __( 'Query parameter name of the nonce included in the "cartUrl" field', 'wp-graphql-woocommerce' )
|
||||
. ( $cart_url_hardcoded ? __( ' This setting is disabled. The "CART_URL_NONCE_PARAM" flag has been set with code', 'wp-graphql-woocommerce' ) : '' ),
|
||||
'type' => 'text',
|
||||
'value' => $cart_url_hardcoded ? CART_URL_NONCE_PARAM : woographql_setting( 'cart_url_nonce_param', '_wc_cart' ),
|
||||
'disabled' => defined( 'CART_URL_NONCE_PARAM' ) || ! in_array( 'cart_url', $enabled_authorizing_url_fields, true ),
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
$other_nonces = self::get_other_nonce_values( 'cart_url' );
|
||||
if ( in_array( $value, $other_nonces, true ) ) {
|
||||
add_settings_error(
|
||||
'cart_url_nonce_param',
|
||||
'unique',
|
||||
__( 'The <strong>Cart URL nonce name</strong> field must be unique', 'wp-graphql-woocommerce' ),
|
||||
'error'
|
||||
);
|
||||
|
||||
return '_wc_cart';
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'checkout_url_nonce_param',
|
||||
'label' => __( 'Checkout URL nonce name', 'wp-graphql-woocommerce' ),
|
||||
'desc' => __( 'Query parameter name of the nonce included in the "checkoutUrl" field', 'wp-graphql-woocommerce' )
|
||||
. ( $checkout_url_hardcoded ? __( ' This setting is disabled. The "CHECKOUT_URL_NONCE_PARAM" flag has been set with code', 'wp-graphql-woocommerce' ) : '' ),
|
||||
'type' => 'text',
|
||||
'value' => $checkout_url_hardcoded ? CHECKOUT_URL_NONCE_PARAM : woographql_setting( 'checkout_url_nonce_param', '_wc_checkout' ),
|
||||
'disabled' => defined( 'CHECKOUT_URL_NONCE_PARAM' ) || ! in_array( 'checkout_url', $enabled_authorizing_url_fields, true ),
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
$other_nonces = self::get_other_nonce_values( 'checkout_url' );
|
||||
if ( in_array( $value, $other_nonces, true ) ) {
|
||||
add_settings_error(
|
||||
'checkout_url_nonce_param',
|
||||
'unique',
|
||||
__( 'The <strong>Checkout URL nonce name</strong> field must be unique', 'wp-graphql-woocommerce' ),
|
||||
'error'
|
||||
);
|
||||
|
||||
return '_wc_checkout';
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'add_payment_method_url_nonce_param',
|
||||
'label' => __( 'Add Payment Method URL nonce name', 'wp-graphql-woocommerce' ),
|
||||
'desc' => __( 'Query parameter name of the nonce included in the "addPaymentMethodUrl" field', 'wp-graphql-woocommerce' )
|
||||
. ( $add_payment_method_url_hardcoded ? __( ' This setting is disabled. The "ADD_PAYMENT_METHOD_URL_NONCE_PARAM" flag has been set with code', 'wp-graphql-woocommerce' ) : '' ),
|
||||
'type' => 'text',
|
||||
'value' => $add_payment_method_url_hardcoded ? ADD_PAYMENT_METHOD_URL_NONCE_PARAM : woographql_setting( 'add_payment_method_url_nonce_param', '_wc_payment' ),
|
||||
'disabled' => defined( 'ADD_PAYMENT_METHOD_URL_NONCE_PARAM' ) || ! in_array( 'add_payment_method_url', $enabled_authorizing_url_fields, true ),
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
$other_nonces = self::get_other_nonce_values( 'add_payment_method_url' );
|
||||
if ( in_array( $value, $other_nonces, true ) ) {
|
||||
add_settings_error(
|
||||
'add_payment_method_url_nonce_param',
|
||||
'unique',
|
||||
__( 'The <strong>Add Payment Method URL nonce name</strong> field must be unique', 'wp-graphql-woocommerce' ),
|
||||
'error'
|
||||
);
|
||||
|
||||
return '_wc_payment';
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,16 @@ namespace WPGraphQL\WooCommerce;
|
||||
*/
|
||||
class Type_Registry {
|
||||
|
||||
|
||||
/**
|
||||
* Registers WooGraphQL types, connections, unions, and mutations to GraphQL schema
|
||||
*
|
||||
* @param \WPGraphQL\Registry\TypeRegistry $type_registry Instance of the WPGraphQL TypeRegistry.
|
||||
*/
|
||||
public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) {
|
||||
// Enumerations.
|
||||
/**
|
||||
* Enumerations.
|
||||
*/
|
||||
Type\WPEnum\Backorders::register();
|
||||
Type\WPEnum\Catalog_Visibility::register();
|
||||
Type\WPEnum\Countries::register();
|
||||
@@ -43,7 +46,9 @@ class Type_Registry {
|
||||
Type\WPEnum\Id_Type_Enums::register();
|
||||
Type\WPEnum\Cart_Error_Type::register();
|
||||
|
||||
// InputObjects.
|
||||
/**
|
||||
* InputObjects.
|
||||
*/
|
||||
Type\WPInputObject\Cart_Item_Input::register();
|
||||
Type\WPInputObject\Customer_Address_Input::register();
|
||||
Type\WPInputObject\Product_Attribute_Input::register();
|
||||
@@ -58,14 +63,18 @@ class Type_Registry {
|
||||
Type\WPInputObject\Product_Taxonomy_Input::register();
|
||||
Type\WPInputObject\Orderby_Inputs::register();
|
||||
|
||||
// Interfaces.
|
||||
/**
|
||||
* Interfaces.
|
||||
*/
|
||||
Type\WPInterface\Product::register_interface();
|
||||
Type\WPInterface\Attribute::register_interface( $type_registry );
|
||||
Type\WPInterface\Product_Attribute::register_interface( $type_registry );
|
||||
Type\WPInterface\Cart_Error::register_interface( $type_registry );
|
||||
Type\WPInterface\Payment_Token::register_interface( $type_registry );
|
||||
|
||||
// Objects.
|
||||
/**
|
||||
* Objects.
|
||||
*/
|
||||
Type\WPObject\Meta_Data_Type::register();
|
||||
Type\WPObject\Downloadable_Item_Type::register();
|
||||
Type\WPObject\Coupon_Type::register();
|
||||
@@ -90,11 +99,27 @@ class Type_Registry {
|
||||
Type\WPObject\Payment_Token_Types::register();
|
||||
Type\WPObject\Country_State_Type::register();
|
||||
|
||||
// Object fields.
|
||||
/**
|
||||
* Object fields.
|
||||
*/
|
||||
Type\WPObject\Product_Category_Type::register_fields();
|
||||
Type\WPObject\Root_Query::register_fields();
|
||||
|
||||
// Connections.
|
||||
// Register the following fields only if "disable_ql_session_handler" option is not on.
|
||||
$ql_session_handled_enabled = ! WooCommerce_Filters::is_session_handler_disabled();
|
||||
if ( $ql_session_handled_enabled ) {
|
||||
Type\WPObject\Customer_Type::register_session_handler_fields();
|
||||
}
|
||||
|
||||
// Register the following fields only if "disable_ql_session_handler" option is not "on" and some fields under the "enable_authorizing_url_fields" option are "selected".
|
||||
$enabled_url_fields = WooCommerce_Filters::enabled_authorizing_url_fields();
|
||||
if ( $ql_session_handled_enabled && ! empty( $enabled_url_fields ) ) {
|
||||
Type\WPObject\Customer_Type::register_authorizing_url_fields( array_keys( $enabled_url_fields ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connections.
|
||||
*/
|
||||
Connection\Posts::register_connections();
|
||||
Connection\WC_Terms::register_connections();
|
||||
Connection\Comments::register_connections();
|
||||
@@ -108,7 +133,9 @@ class Type_Registry {
|
||||
Connection\Shipping_Methods::register_connections();
|
||||
Connection\Payment_Gateways::register_connections();
|
||||
|
||||
// Mutations.
|
||||
/**
|
||||
* Mutations.
|
||||
*/
|
||||
Mutation\Customer_Register::register_mutation();
|
||||
Mutation\Customer_Update::register_mutation();
|
||||
Mutation\Cart_Add_Item::register_mutation();
|
||||
@@ -135,5 +162,6 @@ class Type_Registry {
|
||||
Mutation\Coupon_Delete::register_mutation();
|
||||
Mutation\Payment_Method_Delete::register_mutation();
|
||||
Mutation\Payment_Method_Set_Default::register_mutation();
|
||||
Mutation\Update_Session::register_mutation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
namespace WPGraphQL\WooCommerce;
|
||||
|
||||
use WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce as WooGraphQL;
|
||||
|
||||
/**
|
||||
* Class WooCommerce_Filters
|
||||
*/
|
||||
@@ -43,7 +45,43 @@ class WooCommerce_Filters {
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_session_handler_disabled() {
|
||||
return 'on' === woographql_setting( 'disable_ql_session_handler', 'off' );
|
||||
return defined( 'NO_QL_SESSION_HANDLER' ) || 'on' === woographql_setting( 'disable_ql_session_handler', 'off' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of enabled authorizing URL field slugs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function enabled_authorizing_url_fields() {
|
||||
if ( defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' ) ) {
|
||||
return apply_filters(
|
||||
'woographql_enabled_authorizing_url_fields',
|
||||
[
|
||||
'cart_url' => 'cart_url',
|
||||
'checkout_url' => 'checkout_url',
|
||||
'add_payment_method_url' => 'add_payment_method_url',
|
||||
]
|
||||
);
|
||||
}
|
||||
return woographql_setting( 'enable_authorizing_url_fields', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the nonce query parameter name for the provided field.
|
||||
*
|
||||
* @param string $field URL field slug.
|
||||
*
|
||||
* @return string null
|
||||
*/
|
||||
public static function get_authorizing_url_nonce_param_name( $field ) {
|
||||
$flag_name = strtoupper( $field );
|
||||
$hardcoded_name = defined( "{$flag_name}_NONCE_PARAM" ) ? constant( "{$flag_name}_NONCE_PARAM" ) : false;
|
||||
if ( ! empty( $hardcoded_name ) ) {
|
||||
return $hardcoded_name;
|
||||
}
|
||||
|
||||
return woographql_setting( "{$field}_nonce_param", null );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +93,11 @@ class WooCommerce_Filters {
|
||||
public static function woocommerce_session_handler( $session_class ) {
|
||||
if ( \WPGraphQL\Router::is_graphql_http_request() ) {
|
||||
$session_class = '\WPGraphQL\WooCommerce\Utils\QL_Session_Handler';
|
||||
} elseif ( WooGraphQL::auth_router_is_enabled() ) {
|
||||
require_once get_includes_directory() . 'utils/class-protected-router.php';
|
||||
require_once get_includes_directory() . 'utils/class-transfer-session-handler.php';
|
||||
|
||||
$session_class = Utils\Protected_Router::is_auth_request() ? '\WPGraphQL\WooCommerce\Utils\Transfer_Session_Handler' : $session_class;
|
||||
}
|
||||
|
||||
return $session_class;
|
||||
|
||||
@@ -302,6 +302,7 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
|
||||
require $include_directory_path . 'mutation/class-review-update.php';
|
||||
require $include_directory_path . 'mutation/class-payment-method-delete.php';
|
||||
require $include_directory_path . 'mutation/class-payment-method-set-default.php';
|
||||
require $include_directory_path . 'mutation/class-update-session.php';
|
||||
|
||||
// Include connection class/function files.
|
||||
require $include_directory_path . 'connection/wc-cpt-connection-args.php';
|
||||
@@ -376,6 +377,26 @@ if ( ! class_exists( '\WPGraphQL\WooCommerce\WP_GraphQL_WooCommerce' ) ) :
|
||||
}//end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any authorizing urls are enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function auth_router_is_enabled() {
|
||||
return defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' )
|
||||
|| ! empty( array_keys( woographql_setting( 'enable_authorizing_url_fields', [] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import and setups Protected_Router class instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function load_auth_router() {
|
||||
require get_includes_directory() . 'utils/class-protected-router.php';
|
||||
add_action( 'after_setup_theme', [ Utils\Protected_Router::class, 'instance' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up WooGraphQL schema.
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,7 @@ class Customer extends Model {
|
||||
if ( empty( $this->fields ) ) {
|
||||
$this->fields = [
|
||||
'ID' => function() {
|
||||
return ( ! empty( $this->data->get_id() ) ) ? $this->data->get_id() : \WC()->session->_customer_id;
|
||||
return ( ! empty( $this->data->get_id() ) ) ? $this->data->get_id() : \WC()->session->get_customer_id();
|
||||
},
|
||||
'id' => function() {
|
||||
return ( ! empty( $this->data->get_id() ) )
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* Mutation - updateSession
|
||||
*
|
||||
* Registers mutation for updating session meta data.
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Mutation
|
||||
* @since 0.12.5
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Mutation;
|
||||
|
||||
use GraphQL\Error\UserError;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use WPGraphQL\AppContext;
|
||||
use WPGraphQL\WooCommerce\Data\Mutation\Cart_Mutation;
|
||||
use WPGraphQL\WooCommerce\Model\Customer;
|
||||
|
||||
/**
|
||||
* Class - Update_Session
|
||||
*/
|
||||
class Update_Session {
|
||||
|
||||
/**
|
||||
* Registers mutation
|
||||
*/
|
||||
public static function register_mutation() {
|
||||
register_graphql_mutation(
|
||||
'updateSession',
|
||||
[
|
||||
'inputFields' => self::get_input_fields(),
|
||||
'outputFields' => self::get_output_fields(),
|
||||
'mutateAndGetPayload' => self::mutate_and_get_payload(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the mutation input field configuration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_input_fields() {
|
||||
return [
|
||||
'sessionData' => [
|
||||
'type' => [ 'list_of' => 'MetaDataInput' ],
|
||||
'description' => __( 'Data to be persisted in the session.', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the mutation output field configuration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_output_fields() {
|
||||
return [
|
||||
'session' => [
|
||||
'type' => [ 'list_of' => 'MetaData' ],
|
||||
'resolve' => function ( $payload ) {
|
||||
$session_data = \WC()->session->get_session_data();
|
||||
$session = [];
|
||||
foreach ( $session_data as $key => $value ) {
|
||||
$meta = new \stdClass();
|
||||
$meta->id = null;
|
||||
$meta->key = $key;
|
||||
$meta->value = maybe_unserialize( $value );
|
||||
$session[] = $meta;
|
||||
}
|
||||
|
||||
return $session;
|
||||
},
|
||||
],
|
||||
'customer' => [
|
||||
'type' => 'Customer',
|
||||
'resolve' => function () {
|
||||
return new Customer( 'session' );
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the mutation data modification closure.
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function mutate_and_get_payload() {
|
||||
return function( $input, AppContext $context, ResolveInfo $info ) {
|
||||
Cart_Mutation::check_session_token();
|
||||
|
||||
// Guard against missing input.
|
||||
if ( empty( $input['sessionData'] ) ) {
|
||||
throw new UserError( __( 'No session data provided', 'wp-graphql-woocommerce' ) );
|
||||
}
|
||||
$session_data_input = $input['sessionData'];
|
||||
|
||||
// Save session data input.
|
||||
foreach ( $session_data_input as $meta ) {
|
||||
\WC()->session->set( $meta['key'], $meta['value'] );
|
||||
}
|
||||
|
||||
if ( is_a( \WC()->session, '\WC_Session_Handler' ) ) {
|
||||
\WC()->session->save_data();
|
||||
}
|
||||
|
||||
do_action( 'woographql_update_session', true );
|
||||
|
||||
// Process errors or return successful.
|
||||
$notices = \WC()->session->get( 'wc_notices' );
|
||||
if ( ! empty( $notices['error'] ) ) {
|
||||
$error_messages = implode( ' ', array_column( $notices['error'], 'notice' ) );
|
||||
\wc_clear_notices();
|
||||
throw new UserError( $error_messages );
|
||||
} else {
|
||||
return [ 'status' => 'SUCCESS' ];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ class Customer_Type {
|
||||
'databaseId' => [
|
||||
'type' => 'Int',
|
||||
'description' => __( 'The ID of the customer in the database', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
$database_id = absint( $source->ID );
|
||||
return ! empty( $database_id ) ? $database_id : null;
|
||||
},
|
||||
],
|
||||
'isVatExempt' => [
|
||||
'type' => 'Boolean',
|
||||
@@ -110,8 +114,28 @@ class Customer_Type {
|
||||
'type' => 'Boolean',
|
||||
'description' => __( 'Return the date customer was last updated', 'wp-graphql-woocommerce' ),
|
||||
],
|
||||
|
||||
'metaData' => Meta_Data_Type::get_metadata_field_definition(),
|
||||
'session' => [
|
||||
'type' => [ 'list_of' => 'MetaData' ],
|
||||
'description' => __( 'Session data for the viewing customer', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function ( $source ) {
|
||||
if ( (string) \WC()->session->get_customer_id() === (string) $source->ID ) {
|
||||
$session_data = \WC()->session->get_session_data();
|
||||
$session = [];
|
||||
foreach ( $session_data as $key => $value ) {
|
||||
$meta = new \stdClass();
|
||||
$meta->id = null;
|
||||
$meta->key = $key;
|
||||
$meta->value = maybe_unserialize( $value );
|
||||
$session[] = $meta;
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
throw new UserError( __( 'It\'s not possible to access another user\'s session data', 'wp-graphql-woocommerce' ) );
|
||||
},
|
||||
],
|
||||
],
|
||||
$other_fields,
|
||||
);
|
||||
@@ -155,6 +179,8 @@ class Customer_Type {
|
||||
|
||||
/**
|
||||
* Registers Customer WPObject type and related fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
register_graphql_object_type(
|
||||
@@ -181,46 +207,6 @@ class Customer_Type {
|
||||
]
|
||||
);
|
||||
|
||||
// Register session token fields if QL_Session_Handler is enabled.
|
||||
if ( 'off' === woographql_setting( 'disable_ql_session_handler', 'off' ) ) {
|
||||
/**
|
||||
* Register the "sessionToken" field to the "Customer" type.
|
||||
*/
|
||||
register_graphql_field(
|
||||
'Customer',
|
||||
'sessionToken',
|
||||
[
|
||||
'type' => 'String',
|
||||
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
if ( \get_current_user_id() === $source->ID || 'guest' === $source->id ) {
|
||||
return apply_filters( 'graphql_customer_session_token', \WC()->session->build_token() );
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
]
|
||||
);
|
||||
/**
|
||||
* Register the "wooSessionToken" field to the "User" type.
|
||||
*/
|
||||
register_graphql_field(
|
||||
'User',
|
||||
'wooSessionToken',
|
||||
[
|
||||
'type' => 'String',
|
||||
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
if ( \get_current_user_id() === $source->userId ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
return apply_filters( 'graphql_customer_session_token', \WC()->session->build_token() );
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
]
|
||||
);
|
||||
}//end if
|
||||
|
||||
/**
|
||||
* Register "availablePaymentMethods" field to "Customer" type.
|
||||
*/
|
||||
@@ -275,4 +261,211 @@ class Customer_Type {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers fields that require the "QL_Session_Handler" class to work.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_session_handler_fields() {
|
||||
/**
|
||||
* Register the "sessionToken" field to the "Customer" type.
|
||||
*/
|
||||
register_graphql_field(
|
||||
'Customer',
|
||||
'sessionToken',
|
||||
[
|
||||
'type' => 'String',
|
||||
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
if ( \get_current_user_id() === $source->ID || 'guest' === $source->id ) {
|
||||
return apply_filters( 'graphql_customer_session_token', \WC()->session->build_token() );
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
]
|
||||
);
|
||||
/**
|
||||
* Register the "wooSessionToken" field to the "User" type.
|
||||
*/
|
||||
register_graphql_field(
|
||||
'User',
|
||||
'wooSessionToken',
|
||||
[
|
||||
'type' => 'String',
|
||||
'description' => __( 'A JWT token that can be used in future requests to for WooCommerce session identification', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
if ( \get_current_user_id() === $source->userId ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
return apply_filters( 'graphql_customer_session_token', \WC()->session->build_token() );
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers selected authorizing_url_fields
|
||||
*
|
||||
* @param array $fields_to_register Slugs of fields.
|
||||
* @return void
|
||||
*/
|
||||
public static function register_authorizing_url_fields( $fields_to_register ) {
|
||||
if ( in_array( 'cart_url', $fields_to_register, true ) ) {
|
||||
register_graphql_fields(
|
||||
'Customer',
|
||||
[
|
||||
'cartUrl' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'A nonced link to the cart page. By default, it expires in 1 hour.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
// Get current customer and user ID.
|
||||
$customer_id = $source->ID;
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Return null if current user not user being queried.
|
||||
if ( 0 !== $current_user_id && $current_user_id !== $customer_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build nonced url as an unauthenticated user.
|
||||
$nonce_name = woographql_setting( 'cart_url_nonce_param', '_wc_cart' );
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'session_id' => $customer_id,
|
||||
$nonce_name => woographql_create_nonce( "load-cart_{$customer_id}" ),
|
||||
],
|
||||
site_url( woographql_setting( 'authorizing_url_endpoint', 'transfer-session' ) )
|
||||
);
|
||||
|
||||
return esc_url_raw( $url );
|
||||
},
|
||||
],
|
||||
'cartNonce' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'A nonce for the cart page. By default, it expires in 1 hour.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
// Get current customer and user ID.
|
||||
$customer_id = $source->ID;
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Return null if current user not user being queried.
|
||||
if ( 0 !== $current_user_id && $current_user_id !== $customer_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return woographql_create_nonce( "load-cart_{$customer_id}" );
|
||||
},
|
||||
],
|
||||
]
|
||||
);
|
||||
}//end if
|
||||
|
||||
if ( in_array( 'checkout_url', $fields_to_register, true ) ) {
|
||||
register_graphql_fields(
|
||||
'Customer',
|
||||
[
|
||||
'checkoutUrl' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'A nonce link to the checkout page for session user. Expires in 24 hours.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
// Get current customer and user ID.
|
||||
$customer_id = $source->ID;
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Return null if current user not user being queried.
|
||||
if ( 0 !== $current_user_id && $current_user_id !== $customer_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build nonced url as an unauthenticated user.
|
||||
$nonce_name = woographql_setting( 'checkout_url_nonce_param', '_wc_checkout' );
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'session_id' => $customer_id,
|
||||
$nonce_name => woographql_create_nonce( "load-checkout_{$customer_id}" ),
|
||||
],
|
||||
site_url( woographql_setting( 'authorizing_url_endpoint', 'transfer-session' ) )
|
||||
);
|
||||
|
||||
return esc_url_raw( $url );
|
||||
},
|
||||
],
|
||||
'checkoutNonce' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'A nonce for the checkout page. By default, it expires in 1 hour.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
// Get current customer and user ID.
|
||||
$customer_id = $source->ID;
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Return null if current user not user being queried.
|
||||
if ( 0 !== $current_user_id && $current_user_id !== $customer_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return woographql_create_nonce( "load-checkout_{$customer_id}" );
|
||||
},
|
||||
],
|
||||
]
|
||||
);
|
||||
}//end if
|
||||
|
||||
if ( in_array( 'add_payment_method_url', $fields_to_register, true ) ) {
|
||||
register_graphql_fields(
|
||||
'Customer',
|
||||
[
|
||||
'addPaymentMethodUrl' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'A nonce link to the add payment method page for the authenticated user. Expires in 24 hours.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get current customer and user ID.
|
||||
$customer_id = $source->ID;
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Return null if current user not user being queried.
|
||||
if ( $current_user_id !== $customer_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build nonced url as an unauthenticated user.
|
||||
$nonce_name = woographql_setting( 'add_payment_method_url_nonce_param', '_wc_payment' );
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'session_id' => $customer_id,
|
||||
$nonce_name => woographql_create_nonce( "load-account_{$customer_id}" ),
|
||||
],
|
||||
site_url( woographql_setting( 'authorizing_url_endpoint', 'transfer-session' ) )
|
||||
);
|
||||
|
||||
return esc_url_raw( $url );
|
||||
},
|
||||
],
|
||||
'addPaymentMethodNonce' => [
|
||||
'type' => 'String',
|
||||
'description' => __( 'A nonce for the add payment method page. By default, it expires in 1 hour.', 'wp-graphql-woocommerce' ),
|
||||
'resolve' => function( $source ) {
|
||||
// Get current customer and user ID.
|
||||
$customer_id = $source->ID;
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
// Return null if current user not user being queried.
|
||||
if ( 0 !== $current_user_id && $current_user_id !== $customer_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return woographql_create_nonce( "load-account_{$customer_id}" );
|
||||
},
|
||||
],
|
||||
]
|
||||
);
|
||||
}//end if
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up the auth endpoint
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Utils
|
||||
* @since 0.12.5
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Utils;
|
||||
|
||||
use WPGraphQL\WooCommerce\WooCommerce_Filters;
|
||||
|
||||
/**
|
||||
* Class Protected_Router
|
||||
*/
|
||||
class Protected_Router {
|
||||
|
||||
/**
|
||||
* Stores the instance of the Protected_Router class
|
||||
*
|
||||
* @var Protected_Router The one true Protected_Router
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* The default route
|
||||
*
|
||||
* @var string $route
|
||||
*/
|
||||
public static $default_route = 'transfer-session';
|
||||
|
||||
/**
|
||||
* Sets the route to use as the endpoint
|
||||
*
|
||||
* @var string $route
|
||||
*/
|
||||
public static $route = null;
|
||||
|
||||
/**
|
||||
* Set the default status code to 200.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $http_status_code = 200;
|
||||
|
||||
/**
|
||||
* Protected_Router constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
self::$route = woographql_setting( 'authorizing_url_endpoint', apply_filters( 'woographql_authorizing_url_endpoint', self::$default_route ) );
|
||||
/**
|
||||
* Create the rewrite rule for the route
|
||||
*/
|
||||
add_action( 'init', [ $this, 'add_rewrite_rule' ], 10 );
|
||||
|
||||
/**
|
||||
* Add the query var for the route
|
||||
*/
|
||||
add_filter( 'query_vars', [ $this, 'add_query_var' ], 1, 1 );
|
||||
|
||||
/**
|
||||
* Redirects the route to the graphql processor
|
||||
*/
|
||||
add_action( 'pre_get_posts', [ $this, 'resolve_request' ], 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Protected_Router Instance.
|
||||
*
|
||||
* @return Protected_Router
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( ! isset( self::$instance ) && ! ( is_a( self::$instance, __CLASS__ ) ) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
// Return the Protected_Router Instance.
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw error on object clone.
|
||||
* The whole idea of the singleton design pattern is that there is a single object
|
||||
* therefore, we don't want the object to be cloned.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone() {
|
||||
// Cloning instances of the class is forbidden.
|
||||
_doing_it_wrong( __FUNCTION__, esc_html__( 'Protected_Router class should not be cloned.', 'wp-graphql-woocommerce' ), esc_html( WPGRAPHQL_WOOCOMMERCE_VERSION ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable unserializing of the class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup() {
|
||||
// De-serializing instances of the class is forbidden.
|
||||
_doing_it_wrong( __FUNCTION__, esc_html__( 'De-serializing instances of the Protected_Router class is not allowed', 'wp-graphql-woocommerce' ), esc_html( WPGRAPHQL_WOOCOMMERCE_VERSION ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds rewrite rule for the route endpoint
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_rewrite_rule() {
|
||||
add_rewrite_rule(
|
||||
self::$route . '/?$',
|
||||
'index.php?' . self::$route . '=true',
|
||||
'top'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the query_var for the route
|
||||
*
|
||||
* @param array $query_vars The array of whitelisted query variables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_query_var( $query_vars ) {
|
||||
$query_vars[] = self::$route;
|
||||
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the current request is a request to download the plugin.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_auth_request() {
|
||||
$is_auth_request = false;
|
||||
if ( isset( $_GET[ self::$route ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
|
||||
$is_auth_request = true;
|
||||
} else {
|
||||
// Check the server to determine if the auth endpoint is being requested.
|
||||
if ( isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$host = wp_unslash( $_SERVER['HTTP_HOST'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$uri = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
if ( ! is_string( $host ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! is_string( $uri ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parsed_site_url = wp_parse_url( site_url( self::$route ), PHP_URL_PATH );
|
||||
$auth_url = ! empty( $parsed_site_url ) ? wp_unslash( $parsed_site_url ) : self::$route;
|
||||
$parsed_request_url = wp_parse_url( $uri, PHP_URL_PATH );
|
||||
$request_url = ! empty( $parsed_request_url ) ? wp_unslash( $parsed_request_url ) : '';
|
||||
|
||||
// Determine if the route is indeed a download request.
|
||||
$is_auth_request = false !== strpos( $request_url, $auth_url );
|
||||
}//end if
|
||||
}//end if
|
||||
|
||||
/**
|
||||
* Filter whether the request is a download request. Default is false.
|
||||
*
|
||||
* @param boolean $is_download_request Whether the request is a request to download the plugin. Default false.
|
||||
*/
|
||||
return apply_filters( 'woographql_is_auth_request', $is_auth_request );
|
||||
}
|
||||
|
||||
/**
|
||||
* This resolves the http request and ensures that WordPress can respond with the appropriate
|
||||
* response instead of responding with a template from the standard WordPress Template
|
||||
* Loading process
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resolve_request() {
|
||||
|
||||
/**
|
||||
* Access the $wp_query object
|
||||
*/
|
||||
global $wp_query;
|
||||
|
||||
/**
|
||||
* Ensure we're on the registered route for graphql route
|
||||
*/
|
||||
if ( ! $this->is_auth_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set is_home to false
|
||||
*/
|
||||
$wp_query->is_home = false;
|
||||
|
||||
/**
|
||||
* Process the GraphQL query Request
|
||||
*/
|
||||
$this->process_auth_request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of all the valid nonce names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_nonce_names() {
|
||||
$enabled_authorizing_url_fields = WooCommerce_Filters::enabled_authorizing_url_fields();
|
||||
if ( empty( $enabled_authorizing_url_fields ) ) {
|
||||
return [];
|
||||
}
|
||||
$nonce_names = [];
|
||||
foreach ( array_keys( $enabled_authorizing_url_fields ) as $field ) {
|
||||
$nonce_names[ $field ] = WooCommerce_Filters::get_authorizing_url_nonce_param_name( $field );
|
||||
}
|
||||
return array_filter( $nonce_names );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce action prefix for the provided field.
|
||||
*
|
||||
* @param string $field Field.
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_nonce_prefix( $field ) {
|
||||
switch ( $field ) {
|
||||
case 'cart_url':
|
||||
return 'load-cart_';
|
||||
case 'checkout_url':
|
||||
return 'load-checkout_';
|
||||
case 'add_payment_method_url':
|
||||
return 'load-account_';
|
||||
default:
|
||||
return apply_filters( 'woographql_auth_nonce_prefix', null, $field, $this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target endpoint url for the provided field.
|
||||
*
|
||||
* @param string $field Field.
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_target_endpoint( $field ) {
|
||||
switch ( $field ) {
|
||||
case 'cart_url':
|
||||
return wc_get_endpoint_url( 'cart' );
|
||||
case 'checkout_url':
|
||||
return wc_get_endpoint_url( 'checkout' );
|
||||
case 'add_payment_method_url':
|
||||
return wc_get_account_endpoint_url( 'add-payment-method' );
|
||||
default:
|
||||
return apply_filters( 'woographql_auth_target_endpoint', null, $field, $this );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to homepage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function redirect_to_home() {
|
||||
status_header( 404 );
|
||||
wp_safe_redirect( home_url() );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send stable version of plugin to download.
|
||||
*
|
||||
* @throws \Exception Session not found.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function process_auth_request() {
|
||||
// Bail early if session ID or nonce not found.
|
||||
$nonce_names = $this->get_nonce_names();
|
||||
if ( empty( $nonce_names ) ) {
|
||||
$this->redirect_to_home();
|
||||
}
|
||||
|
||||
$nonce_prefix = null;
|
||||
$session_id = null;
|
||||
$nonce = null;
|
||||
foreach ( $nonce_names as $field => $nonce_param ) {
|
||||
if ( in_array( $nonce_param, array_keys( $_REQUEST ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$nonce_prefix = $this->get_nonce_prefix( $field );
|
||||
$session_id = isset( $_REQUEST['session_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['session_id'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$nonce = isset( $_REQUEST[ $nonce_param ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_param ] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $nonce_prefix ) | empty( $session_id ) | empty( $nonce ) ) {
|
||||
$this->redirect_to_home();
|
||||
}
|
||||
|
||||
// Bail early if session user already authenticated.
|
||||
if ( 0 !== get_current_user_id() && get_current_user_id() === absint( $session_id ) ) {
|
||||
wp_safe_redirect( $this->get_target_endpoint( $field ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Unauthenticate if current user not session user.
|
||||
if ( 0 !== get_current_user_id() ) {
|
||||
wp_clear_auth_cookie();
|
||||
wp_set_current_user( 0 );
|
||||
}
|
||||
|
||||
// Verify nonce.
|
||||
if ( ! woographql_verify_nonce( $nonce, $nonce_prefix . $session_id ) ) {
|
||||
$this->redirect_to_home();
|
||||
}
|
||||
|
||||
// If Session ID is a user ID authenticate as session user.
|
||||
if ( 0 !== absint( $session_id ) ) {
|
||||
wp_clear_auth_cookie();
|
||||
wp_set_current_user( $session_id );
|
||||
wp_set_auth_cookie( $session_id );
|
||||
}
|
||||
|
||||
// Read session data connected to session ID.
|
||||
$session_data = \WC()->session->get_session( $session_id );
|
||||
|
||||
// We were passed a session ID, yet no session was found. Let's log this and bail.
|
||||
if ( empty( $session_data ) ) {
|
||||
// TODO: Switch to WC Notices.
|
||||
throw new \Exception( 'Could not locate WooCommerce session on checkout' );
|
||||
}
|
||||
|
||||
// Reinitialize session and save session cookie before redirect.
|
||||
\WC()->session->init_session_cookie();
|
||||
|
||||
// Set the session variable.
|
||||
foreach ( $session_data as $key => $value ) {
|
||||
\WC()->session->set( $key, maybe_unserialize( $value ) );
|
||||
}
|
||||
\WC()->session->set_customer_session_cookie( true );
|
||||
|
||||
// After session has been restored on redirect to destination.
|
||||
wp_safe_redirect( $this->get_target_endpoint( $field ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ class QL_Session_Handler extends WC_Session_Handler {
|
||||
$this->transaction_manager = Session_Transaction_Manager::get( $this );
|
||||
|
||||
add_action( 'woocommerce_set_cart_cookies', [ $this, 'set_customer_session_token' ], 10 );
|
||||
add_action( 'woographql_update_session', [ $this, 'set_customer_session_token' ], 10 );
|
||||
add_action( 'graphql_after_resolve_field', [ $this, 'save_if_dirty' ], 10, 4 );
|
||||
add_action( 'shutdown', [ $this, 'save_data' ] );
|
||||
add_action( 'wp_logout', [ $this, 'destroy_session' ] );
|
||||
@@ -115,7 +116,7 @@ class QL_Session_Handler extends WC_Session_Handler {
|
||||
public function init_session_token() {
|
||||
$token = $this->get_session_token();
|
||||
|
||||
// Process existing session.
|
||||
// Process existing session if not expired or invalid.
|
||||
if ( $token && ! is_wp_error( $token ) ) {
|
||||
$this->_customer_id = $token->data->customer_id;
|
||||
$this->_session_issued = $token->iat;
|
||||
@@ -357,7 +358,7 @@ class QL_Session_Handler extends WC_Session_Handler {
|
||||
$this->_session_expiration = apply_filters(
|
||||
'graphql_woocommerce_cart_session_expire',
|
||||
// Seconds * Minutes * Hours * Days.
|
||||
time() + ( 60 * 60 * 24 * 14 )
|
||||
$this->_session_issued + ( 60 * 60 * 24 * 14 )
|
||||
);
|
||||
// 13 Days.
|
||||
$this->_session_expiring = $this->_session_expiration - ( 60 * 60 * 24 );
|
||||
@@ -426,4 +427,38 @@ class QL_Session_Handler extends WC_Session_Handler {
|
||||
*/
|
||||
public function set_customer_session_cookie( $set ) {}
|
||||
|
||||
/**
|
||||
* Returns "client_session_id". "client_session_id_expiration" is used
|
||||
* to keep "client_session_id" as fresh as possible.
|
||||
*
|
||||
* For the most strict level of security it's highly recommend these values
|
||||
* be set client-side using the `updateSession` mutation.
|
||||
* "client_session_id" in particular should be salted with some
|
||||
* kind of client identifier like the end-user "IP" or "user-agent"
|
||||
* then hashed parodying the tokens generated by
|
||||
* WP's WP_Session_Tokens class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_client_session_id() {
|
||||
// Get client session ID.
|
||||
$client_session_id = $this->get( 'client_session_id', false );
|
||||
$client_session_id_expiration = absint( $this->get( 'client_session_id_expiration', 0 ) );
|
||||
|
||||
// If client session ID valid return it.
|
||||
if ( false !== $client_session_id && time() < $client_session_id_expiration ) {
|
||||
return $client_session_id;
|
||||
}
|
||||
|
||||
// Generate a new client session ID.
|
||||
$client_session_id = uniqid();
|
||||
$client_session_id_expiration = time() + 3600;
|
||||
$this->set( 'client_session_id', $client_session_id );
|
||||
$this->set( 'client_session_id_expiration', $client_session_id_expiration );
|
||||
$this->save_data();
|
||||
|
||||
// Return new client session ID.
|
||||
return $client_session_id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ class Session_Transaction_Manager {
|
||||
'updateItemQuantities',
|
||||
'updateShippingMethod',
|
||||
'updateCustomer',
|
||||
'updateSession',
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -240,7 +241,7 @@ class Session_Transaction_Manager {
|
||||
}
|
||||
|
||||
// Save transaction queue.
|
||||
set_transient( "woo_session_transactions_queue_{$this->session_handler->get_customer_id()}", $queue );
|
||||
set_transient( "woo_session_transactions_queue_{$this->session_handler->get_customer_id()}", $queue, 5 * MINUTE_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles data for the current customers session.
|
||||
*
|
||||
* @package WPGraphQL\WooCommerce\Utils
|
||||
* @since 0.12.5
|
||||
*/
|
||||
|
||||
namespace WPGraphQL\WooCommerce\Utils;
|
||||
|
||||
/**
|
||||
* Class Transfer_Session_Handler
|
||||
*/
|
||||
class Transfer_Session_Handler extends \WC_Session_Handler {
|
||||
/**
|
||||
* Return true, if valid credential exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function verify_auth_request_credentials_exists() {
|
||||
$possible_nonces = array_values( Protected_Router::get_nonce_names() );
|
||||
// Return false if not nonce names set.
|
||||
if ( empty( $possible_nonces ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return false if no matching nonces found in query parameters.
|
||||
$query_params = array_keys( $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( array_intersect( $possible_nonces, $query_params ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "session_id" if proper conditions met.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_posted_session_id() {
|
||||
if ( ! $this->verify_auth_request_credentials_exists() ) {
|
||||
return 0;
|
||||
}
|
||||
if ( ! isset( $_REQUEST['session_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sanitize_text_field( wp_unslash( $_REQUEST['session_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in customer ID from query parameters if specific conditions are met otherwise
|
||||
* a guest ID are generated as usual.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_customer_id() {
|
||||
$session_id = $this->get_posted_session_id();
|
||||
if ( 0 !== $session_id ) {
|
||||
return $session_id;
|
||||
}
|
||||
|
||||
return parent::generate_customer_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns client session ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_client_session_id() {
|
||||
$session_id = $this->get_posted_session_id();
|
||||
$session_data = 0 !== $session_id ? $this->get_session( $session_id ) : null;
|
||||
|
||||
if ( ! empty( $session_data ) ) {
|
||||
$client_session_id = $session_data['client_session_id'];
|
||||
$client_session_id_expiration = $session_data['client_session_id_expiration'];
|
||||
} else {
|
||||
$client_session_id = $this->get( 'client_session_id', false );
|
||||
$client_session_id_expiration = absint( $this->get( 'client_session_id_expiration', 0 ) );
|
||||
}
|
||||
|
||||
if ( false !== $client_session_id && time() < $client_session_id_expiration ) {
|
||||
return $client_session_id;
|
||||
}
|
||||
|
||||
$client_session_id = '';
|
||||
|
||||
return $client_session_id;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,8 +4,8 @@
|
||||
# Any changes to the directives between these markers will be overwritten.
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
RewriteBase /
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
RewriteRule ^index\.php$ - [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="WordPress Coding Standards based custom ruleset for your plugin">
|
||||
<config name="installed_paths" value="vendor/wp-coding-standards/wpcs" />
|
||||
<config name="installed_paths" value="vendor/automattic/vipwpcs,vendor/wp-coding-standards/wpcs" />
|
||||
<description>Generally-applicable sniffs for WordPress plugins.</description>
|
||||
|
||||
<!-- How to scan -->
|
||||
|
||||
+13
-1
@@ -12,6 +12,19 @@ if ( ! defined( 'WPGRAPHQL_WOOCOMMERCE_AUTOLOAD' ) && false !== getenv( 'WPGRAPH
|
||||
define( 'WPGRAPHQL_WOOCOMMERCE_AUTOLOAD', true );
|
||||
}
|
||||
|
||||
if ( ! defined( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS' ) ) {
|
||||
define( 'WPGRAPHQL_WOOCOMMERCE_ENABLE_AUTH_URLS', true );
|
||||
}
|
||||
if ( ! defined( 'CART_URL_NONCE_PARAM' ) ) {
|
||||
define( 'CART_URL_NONCE_PARAM', '_wc_cart' );
|
||||
}
|
||||
if ( ! defined( 'CHECKOUT_URL_NONCE_PARAM' ) ) {
|
||||
define( 'CHECKOUT_URL_NONCE_PARAM', '_wc_checkout' );
|
||||
}
|
||||
if ( ! defined( 'ADD_PAYMENT_METHOD_URL_NONCE_PARAM' ) ) {
|
||||
define( 'ADD_PAYMENT_METHOD_URL_NONCE_PARAM', '_wc_payment' );
|
||||
}
|
||||
|
||||
if ( ! defined( 'GRAPHQL_JWT_AUTH_SECRET_KEY' ) ) {
|
||||
define( 'GRAPHQL_JWT_AUTH_SECRET_KEY', 'testingtesting123' );
|
||||
}
|
||||
@@ -23,4 +36,3 @@ if ( ! defined( 'STRIPE_API_PUBLISHABLE_KEY' ) && false !== getenv( 'STRIPE_API_
|
||||
if ( ! defined( 'STRIPE_API_SECRET_KEY' ) && false !== getenv( 'STRIPE_API_SECRET_KEY' ) ) {
|
||||
define( 'STRIPE_API_SECRET_KEY', getenv( 'STRIPE_API_SECRET_KEY' ) );
|
||||
}
|
||||
|
||||
|
||||
@@ -705,6 +705,37 @@ class GraphQLE2E extends \Codeception\Module {
|
||||
$product_catalog[ $product['post_title'] ] = $product_id;
|
||||
}
|
||||
|
||||
// Create cart page.
|
||||
$wpdb = $this->getModule( 'WPDb' );
|
||||
$cart_page_id = $wpdb->havePostInDatabase(
|
||||
[
|
||||
'post_type' => 'page',
|
||||
'post_title' => 'Cart',
|
||||
'post_name' => 'cart',
|
||||
'post_author' => 1,
|
||||
'post_content' => '[woocommerce_cart]',
|
||||
'post_status' => 'publish',
|
||||
]
|
||||
);
|
||||
update_option( 'woocommerce_cart_page_id', $cart_page_id );
|
||||
$checkout_page_id = $wpdb->havePostInDatabase(
|
||||
[
|
||||
'post_type' => 'page',
|
||||
'post_title' => 'Checkout',
|
||||
'post_name' => 'checkout',
|
||||
'post_author' => 1,
|
||||
'post_content' => '[woocommerce_checkout]',
|
||||
'post_status' => 'publish',
|
||||
]
|
||||
);
|
||||
update_option( 'woocommerce_checkout_page_id', $checkout_page_id );
|
||||
|
||||
global $wp_rewrite;
|
||||
// Set the permalink structure
|
||||
$wp_rewrite->set_permalink_structure( '/%postname%/' );
|
||||
// Flush the rules and tell it to write htaccess
|
||||
$wp_rewrite->flush_rules( true );
|
||||
|
||||
return $product_catalog;
|
||||
}
|
||||
|
||||
@@ -790,6 +821,7 @@ class GraphQLE2E extends \Codeception\Module {
|
||||
*/
|
||||
public function haveAProductInTheDatabase( $args, &$product_id, $term = 'simple', &$term_id = 0 ) {
|
||||
$wpdb = $this->getModule( 'WPDb' );
|
||||
|
||||
// Create Product
|
||||
$product_id = $wpdb->havePostInDatabase(
|
||||
array_replace_recursive(
|
||||
@@ -867,4 +899,22 @@ class GraphQLE2E extends \Codeception\Module {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function verifyRedirect( $startUrl, $endUrl, $redirectCode = 301 ) {
|
||||
$phpBrowser = $this->getModule( 'WPBrowser' );
|
||||
$guzzle = $phpBrowser->client;
|
||||
|
||||
// Disable the following of redirects
|
||||
$guzzle->followRedirects( false );
|
||||
|
||||
$phpBrowser->_loadPage( 'GET', $startUrl );
|
||||
$response = $guzzle->getInternalResponse();
|
||||
$responseCode = $response->getStatusCode();
|
||||
$locationHeader = $response->getHeader( 'Location' );
|
||||
|
||||
$this->assertEquals( $responseCode, $redirectCode );
|
||||
$this->assertEquals( $endUrl, $locationHeader );
|
||||
|
||||
$guzzle->followRedirects( true );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
$I = new AcceptanceTester( $scenario );
|
||||
// Create products
|
||||
$product_catalog = $I->getCatalog();
|
||||
|
||||
// Flush permalinks.
|
||||
$I->loginAsAdmin();
|
||||
$I->amOnAdminPage( 'options-permalink.php' );
|
||||
$I->click( '#submit' );
|
||||
$I->logOut();
|
||||
|
||||
// Make quick helper for managing the session token.
|
||||
$request_headers = function () use ( $I, &$last_request_headers ) {
|
||||
$last_request_headers = [
|
||||
'woocommerce-session' => 'Session ' . $I->wantHTTPResponseHeaders( 'woocommerce-session' ),
|
||||
];
|
||||
|
||||
return $last_request_headers;
|
||||
};
|
||||
|
||||
// Begin test.
|
||||
$I->wantTo( 'add items to the cart' );
|
||||
|
||||
/**
|
||||
* Add "T-Shirt" to cart and confirm response data.
|
||||
*/
|
||||
$add_to_cart_input = [
|
||||
'clientMutationId' => 'someId',
|
||||
'productId' => $product_catalog['t-shirt'],
|
||||
'quantity' => 3,
|
||||
];
|
||||
|
||||
$success = $I->addToCart( $add_to_cart_input );
|
||||
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'addToCart', $success['data'] );
|
||||
$I->assertArrayHasKey( 'cartItem', $success['data']['addToCart'] );
|
||||
$I->assertArrayHasKey( 'key', $success['data']['addToCart']['cartItem'] );
|
||||
$shirt_key = $success['data']['addToCart']['cartItem']['key'];
|
||||
|
||||
|
||||
$I->wantTo( 'Set "client_session_id" and get nonced cart URL' );
|
||||
$update_session_mutation = '
|
||||
mutation($input: UpdateSessionInput!) {
|
||||
updateSession(input: $input) {
|
||||
session {
|
||||
id
|
||||
key
|
||||
value
|
||||
}
|
||||
customer { cartUrl }
|
||||
}
|
||||
}
|
||||
';
|
||||
$success = $I->sendGraphQLRequest(
|
||||
$update_session_mutation,
|
||||
[
|
||||
'sessionData' => [
|
||||
[
|
||||
'key' => 'client_session_id',
|
||||
'value' => 'test-client-session-id',
|
||||
],
|
||||
[
|
||||
'key' => 'client_session_id_expiration',
|
||||
'value' => (string) ( time() + 3600 ),
|
||||
],
|
||||
],
|
||||
],
|
||||
$request_headers()
|
||||
);
|
||||
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'updateSession', $success['data'] );
|
||||
$I->assertArrayHasKey( 'session', $success['data']['updateSession'] );
|
||||
$session = $success['data']['updateSession']['session'];
|
||||
$session = array_column( $session, 'value', 'key' );
|
||||
$I->assertEquals( $session['client_session_id'], 'test-client-session-id' );
|
||||
$I->assertArrayHasKey( 'customer', $success['data']['updateSession'] );
|
||||
$I->assertArrayHasKey( 'cartUrl', $success['data']['updateSession']['customer'] );
|
||||
$cart_url = $success['data']['updateSession']['customer']['cartUrl'];
|
||||
|
||||
$I->wantTo( 'Go cart page and confirm empty and session not seen' );
|
||||
$I->amOnPage( '/cart' );
|
||||
$I->see( 'Your cart is currently empty.' );
|
||||
|
||||
$I->wantTo( 'Authenticate with cart url and confirm page redirect' );
|
||||
$I->stopFollowingRedirects();
|
||||
$I->amOnUrl( $cart_url );
|
||||
$I->seeResponseCodeIs( 302 );
|
||||
$I->followRedirect();
|
||||
$I->seeInCurrentUrl( '/cart/' );
|
||||
$I->startFollowingRedirects();
|
||||
|
||||
$I->wantTo( 'Confirm cart not empty and T-shirt in cart.' );
|
||||
$I->see( 'T-Shirt' );
|
||||
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
class ProtectedRouterCest {
|
||||
private $product_catalog;
|
||||
|
||||
public function _before( FunctionalTester $I ) {
|
||||
// Create Products
|
||||
$this->product_catalog = $I->getCatalog();
|
||||
|
||||
// Flush permalinks.
|
||||
$I->loginAsAdmin();
|
||||
$I->amOnAdminPage( 'options-permalink.php' );
|
||||
$I->click( '#submit' );
|
||||
$I->logOut();
|
||||
|
||||
if ( ! defined( 'GRAPHQL_WOOCOMMERCE_SECRET_KEY' ) ) {
|
||||
define( 'GRAPHQL_WOOCOMMERCE_SECRET_KEY', 'testestestestest' );
|
||||
}
|
||||
}
|
||||
|
||||
public function _startNewSession( FunctionalTester $I ) {
|
||||
$I->wantTo( 'Start new session by adding an item to the cart' );
|
||||
/**
|
||||
* Add t-shirt to the cart
|
||||
*/
|
||||
$success = $I->addToCart(
|
||||
[
|
||||
'clientMutationId' => 'someId',
|
||||
'productId' => $this->product_catalog['t-shirt'],
|
||||
'quantity' => 5,
|
||||
],
|
||||
);
|
||||
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'addToCart', $success['data'] );
|
||||
$I->assertArrayHasKey( 'cartItem', $success['data']['addToCart'] );
|
||||
$I->assertArrayHasKey( 'key', $success['data']['addToCart']['cartItem'] );
|
||||
$key = $success['data']['addToCart']['cartItem']['key'];
|
||||
|
||||
/**
|
||||
* Assert existence and validity of "woocommerce-session" HTTP header.
|
||||
*/
|
||||
$I->seeHttpHeaderOnce( 'woocommerce-session' );
|
||||
$session_token = $I->grabHttpHeader( 'woocommerce-session' );
|
||||
|
||||
return compact( 'key', 'session_token' );
|
||||
}
|
||||
|
||||
public function _getLastRequestHeaders( $I ) {
|
||||
$headers = [
|
||||
'woocommerce-session' => 'Session ' . $I->wantHTTPResponseHeaders( 'woocommerce-session' ),
|
||||
];
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function tryToProceedToCheckoutPage( FunctionalTester $I ) {
|
||||
$session_data = $this->_startNewSession( $I );
|
||||
$session_token = $session_data['session_token'];
|
||||
// Retrieve and decode token for session_id.
|
||||
JWT::$leeway = 60;
|
||||
$token_data = ! empty( $session_token )
|
||||
? JWT::decode( $session_token, new Key( GRAPHQL_WOOCOMMERCE_SECRET_KEY, 'HS256' ) )
|
||||
: null;
|
||||
$session_token = $token_data->data->customer_id;
|
||||
|
||||
$I->wantTo( 'Get the session checkout URL' );
|
||||
$query = 'query { customer { checkoutNonce } }';
|
||||
$success = $I->sendGraphQLRequest(
|
||||
$query,
|
||||
null,
|
||||
$this->_getLastRequestHeaders( $I )
|
||||
);
|
||||
|
||||
// Assert "checkoutUrl" was received.
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'customer', $success['data'] );
|
||||
$I->assertArrayHasKey( 'checkoutNonce', $success['data']['customer'] );
|
||||
$checkout_nonce = $success['data']['customer']['checkoutNonce'];
|
||||
|
||||
$I->wantTo( 'Go checkout page and confirm session not seen' );
|
||||
$I->amOnPage( '/checkout' );
|
||||
$I->see( 'Your cart is currently empty.' );
|
||||
|
||||
$I->wantTo( 'Authenticate with nonced url and confirm page redirect to checkout page' );
|
||||
$I->stopFollowingRedirects();
|
||||
|
||||
$wp_url = getenv( 'WORDPRESS_URL' );
|
||||
|
||||
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session_token}&_wc_checkout={$checkout_nonce}" );
|
||||
$I->seeResponseCodeIs( 302 );
|
||||
$I->followRedirect();
|
||||
$I->seeInCurrentUrl( '/checkout/' );
|
||||
$I->startFollowingRedirects();
|
||||
|
||||
$I->wantTo( 'Confirm session has been loaded.' );
|
||||
$I->see( 'Checkout' );
|
||||
$I->see( 'Apply Coupon' );
|
||||
$I->see( 'T-Shirt' );
|
||||
}
|
||||
|
||||
|
||||
public function tryToProceedToCheckoutPageWithExpiredUrl( FunctionalTester $I ) {
|
||||
$this->_startNewSession( $I );
|
||||
|
||||
$I->wantTo( 'Get the session checkout URL' );
|
||||
$query = '
|
||||
mutation($input: UpdateSessionInput!) {
|
||||
updateSession(input: $input) {
|
||||
session {
|
||||
id
|
||||
key
|
||||
value
|
||||
}
|
||||
customer { checkoutUrl }
|
||||
}
|
||||
}
|
||||
';
|
||||
$success = $I->sendGraphQLRequest(
|
||||
$query,
|
||||
[
|
||||
'sessionData' => [
|
||||
[
|
||||
'key' => 'client_session_id',
|
||||
'value' => 'test-client-session-id',
|
||||
],
|
||||
],
|
||||
],
|
||||
$this->_getLastRequestHeaders( $I )
|
||||
);
|
||||
|
||||
// Assert updateSession was success.
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'updateSession', $success['data'] );
|
||||
$I->assertArrayHasKey( 'session', $success['data']['updateSession'] );
|
||||
$session = $success['data']['updateSession']['session'];
|
||||
$session = array_column( $session, 'value', 'key' );
|
||||
$I->assertEquals( $session['client_session_id'], 'test-client-session-id' );
|
||||
|
||||
// Assert "checkoutUrl" was received.
|
||||
$I->assertArrayHasKey( 'customer', $success['data']['updateSession'] );
|
||||
$I->assertArrayHasKey( 'checkoutUrl', $success['data']['updateSession']['customer'] );
|
||||
$expired_checkout_url = $success['data']['updateSession']['customer']['checkoutUrl'];
|
||||
|
||||
$I->wantTo( 'Invalidate Checkout URL by updating the "client_session_id"' );
|
||||
$success = $I->sendGraphQLRequest(
|
||||
$query,
|
||||
[
|
||||
'sessionData' => [
|
||||
[
|
||||
'key' => 'client_session_id',
|
||||
'value' => 'new-test-client-session-id',
|
||||
],
|
||||
],
|
||||
],
|
||||
$this->_getLastRequestHeaders( $I )
|
||||
);
|
||||
|
||||
// Assert updateSession was success.
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'updateSession', $success['data'] );
|
||||
$I->assertArrayHasKey( 'session', $success['data']['updateSession'] );
|
||||
$session = $success['data']['updateSession']['session'];
|
||||
$session = array_column( $session, 'value', 'key' );
|
||||
$I->assertEquals( $session['client_session_id'], 'new-test-client-session-id' );
|
||||
|
||||
$I->wantTo( 'Go checkout page and confirm session not seen' );
|
||||
$I->amOnPage( '/checkout' );
|
||||
$I->see( 'Your cart is currently empty.' );
|
||||
|
||||
$I->wantTo( 'Attempt to authenticate with expired url and confirm page redirect to checkout page' );
|
||||
$I->stopFollowingRedirects();
|
||||
$I->amOnUrl( $expired_checkout_url );
|
||||
$I->seeResponseCodeIs( 302 );
|
||||
$I->followRedirect();
|
||||
$I->dontSeeInCurrentUrl( '/checkout/' );
|
||||
$I->startFollowingRedirects();
|
||||
}
|
||||
|
||||
|
||||
public function tryToProceedToCheckoutPageWithInvalidNonce( FunctionalTester $I ) {
|
||||
$session_data = $this->_startNewSession( $I );
|
||||
$session_token = $session_data['session_token'];
|
||||
// Retrieve and decode token for session_id.
|
||||
JWT::$leeway = 60;
|
||||
$token_data = ! empty( $session_token )
|
||||
? JWT::decode( $session_token, new Key( GRAPHQL_WOOCOMMERCE_SECRET_KEY, 'HS256' ) )
|
||||
: null;
|
||||
$session_token = $token_data->data->customer_id;
|
||||
|
||||
$I->wantTo( 'Get the session checkout URL' );
|
||||
$query = 'query { customer { checkoutUrl } }';
|
||||
$success = $I->sendGraphQLRequest(
|
||||
$query,
|
||||
null,
|
||||
$this->_getLastRequestHeaders( $I )
|
||||
);
|
||||
|
||||
// Assert "checkoutUrl" was received.
|
||||
$I->assertArrayNotHasKey( 'errors', $success );
|
||||
$I->assertArrayHasKey( 'data', $success );
|
||||
$I->assertArrayHasKey( 'customer', $success['data'] );
|
||||
$I->assertArrayHasKey( 'checkoutUrl', $success['data']['customer'] );
|
||||
|
||||
$I->wantTo( 'Go checkout page and confirm session not seen' );
|
||||
$I->amOnPage( '/checkout' );
|
||||
$I->see( 'Your cart is currently empty.' );
|
||||
|
||||
$I->wantTo( 'Attempt to authenticate with nonced url and confirm page redirect to checkout page' );
|
||||
$I->stopFollowingRedirects();
|
||||
|
||||
$wp_url = getenv( 'WORDPRESS_URL' );
|
||||
|
||||
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session_token}&_wc_checkout=12345" );
|
||||
$I->seeResponseCodeIs( 302 );
|
||||
$I->followRedirect();
|
||||
$I->dontSeeInCurrentUrl( '/checkout/' );
|
||||
$I->startFollowingRedirects();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
use GraphQLRelay\Relay;
|
||||
class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
|
||||
|
||||
public function expectedCustomerData( $id ) {
|
||||
@@ -609,4 +608,66 @@ class CustomerQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraph
|
||||
|
||||
$this->assertQuerySuccessful( $response, $expected );
|
||||
}
|
||||
|
||||
public function testAuthorizingUrlFields() {
|
||||
// Reinitialize WC session with QL_Session_Handler set.
|
||||
add_filter(
|
||||
'woocommerce_session_handler',
|
||||
function( $session_class ) {
|
||||
return '\WPGraphQL\WooCommerce\Utils\QL_Session_Handler';
|
||||
}
|
||||
);
|
||||
\WC()->initialize_session();
|
||||
|
||||
// Create customer for later use.
|
||||
$customer_id = $this->factory->customer->create();
|
||||
|
||||
// Create auth URLs query.
|
||||
$query = '
|
||||
query($id: ID) {
|
||||
customer(id: $id) {
|
||||
id
|
||||
cartUrl
|
||||
cartNonce
|
||||
checkoutUrl
|
||||
checkoutNonce
|
||||
addPaymentMethodUrl
|
||||
addPaymentMethodNonce
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
/**
|
||||
* Assert NULL values when querying as admin
|
||||
*/
|
||||
$this->loginAsShopManager();
|
||||
$variables = [ 'id' => $this->toRelayId( 'customer', $customer_id ) ];
|
||||
$response = $this->graphql( compact( 'query', 'variables' ) );
|
||||
$expected = [
|
||||
$this->expectedField( 'customer.id', $this->toRelayId( 'customer', $customer_id ) ),
|
||||
$this->expectedField( 'customer.cartUrl', self::IS_NULL ),
|
||||
$this->expectedField( 'customer.cartNonce', self::IS_NULL ),
|
||||
$this->expectedField( 'customer.checkoutUrl', self::IS_NULL ),
|
||||
$this->expectedField( 'customer.checkoutNonce', self::IS_NULL ),
|
||||
$this->expectedField( 'customer.addPaymentMethodUrl', self::IS_NULL ),
|
||||
$this->expectedField( 'customer.addPaymentMethodNonce', self::IS_NULL ),
|
||||
];
|
||||
$this->assertQuerySuccessful( $response, $expected );
|
||||
|
||||
/**
|
||||
* Assert NOT NULL values when querying as admin
|
||||
*/
|
||||
$this->loginAs( $customer_id );
|
||||
$response = $this->graphql( compact( 'query' ) );
|
||||
$expected = [
|
||||
$this->expectedField( 'customer.id', $this->toRelayId( 'customer', $customer_id ) ),
|
||||
$this->expectedField( 'customer.cartUrl', self::NOT_NULL ),
|
||||
$this->expectedField( 'customer.cartNonce', self::NOT_NULL ),
|
||||
$this->expectedField( 'customer.checkoutUrl', self::NOT_NULL ),
|
||||
$this->expectedField( 'customer.checkoutNonce', self::NOT_NULL ),
|
||||
$this->expectedField( 'customer.addPaymentMethodUrl', self::NOT_NULL ),
|
||||
$this->expectedField( 'customer.addPaymentMethodNonce', self::NOT_NULL ),
|
||||
];
|
||||
$this->assertQuerySuccessful( $response, $expected );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class ProtectedRouterTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
|
||||
public function testRouteEndpoint() {
|
||||
/**
|
||||
* Test that the default route is set to "graphql"
|
||||
*/
|
||||
$this->assertEquals( 'transfer-session', apply_filters( 'woographql_authorizing_url_endpoint', \WPGraphQL\WooCommerce\Utils\Protected_Router::$route ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to make sure that the rewrite rules properly include the graphql route
|
||||
*/
|
||||
public function testGraphQLRewriteRule() {
|
||||
global $wp_rewrite;
|
||||
$route = apply_filters( 'woographql_authorizing_url_endpoint', \WPGraphQL\WooCommerce\Utils\Protected_Router::$route );
|
||||
$this->assertArrayHasKey( $route . '/?$', $wp_rewrite->extra_rules_top );
|
||||
}
|
||||
|
||||
public function testAddQueryVar() {
|
||||
$query_vars = [];
|
||||
$router = \WPGraphQL\WooCommerce\Utils\Protected_Router::instance();
|
||||
$actual = $router->add_query_var( $query_vars );
|
||||
$this->assertEquals( $actual, [ apply_filters( 'woographql_authorizing_url_endpoint', \WPGraphQL\WooCommerce\Utils\Protected_Router::$route ) ] );
|
||||
}
|
||||
|
||||
public function testGetNonceNames() {
|
||||
$router = \WPGraphQL\WooCommerce\Utils\Protected_Router::instance();
|
||||
$this->assertEquals(
|
||||
[
|
||||
'cart_url' => '_wc_cart',
|
||||
'checkout_url' => '_wc_checkout',
|
||||
'add_payment_method_url' => '_wc_payment',
|
||||
],
|
||||
$router->get_nonce_names()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetNoncePrefix() {
|
||||
$router = \WPGraphQL\WooCommerce\Utils\Protected_Router::instance();
|
||||
$this->assertEquals( 'load-cart_', $router->get_nonce_prefix( 'cart_url' ) );
|
||||
$this->assertEquals( 'load-checkout_', $router->get_nonce_prefix( 'checkout_url' ) );
|
||||
$this->assertEquals( 'load-account_', $router->get_nonce_prefix( 'add_payment_method_url' ) );
|
||||
$this->assertEquals( null, $router->get_nonce_prefix( 'invalid' ) );
|
||||
}
|
||||
|
||||
public function testGetTargetEndpoint() {
|
||||
$router = \WPGraphQL\WooCommerce\Utils\Protected_Router::instance();
|
||||
$this->assertEquals( wc_get_endpoint_url( 'cart' ), $router->get_target_endpoint( 'cart_url' ) );
|
||||
$this->assertEquals( wc_get_endpoint_url( 'checkout' ), $router->get_target_endpoint( 'checkout_url' ) );
|
||||
$this->assertEquals( wc_get_account_endpoint_url( 'add-payment-method' ), $router->get_target_endpoint( 'add_payment_method_url' ) );
|
||||
$this->assertEquals( null, $router->get_nonce_prefix( 'invalid' ) );
|
||||
}
|
||||
}
|
||||
@@ -148,4 +148,28 @@ class QLSessionHandlerTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGrap
|
||||
|
||||
$this->assertNotEquals( $old_token, $new_token, 'Tokens should not match' );
|
||||
}
|
||||
|
||||
public function test_get_client_session_id() {
|
||||
// Create session handler.
|
||||
$session = new QL_Session_Handler();
|
||||
|
||||
// Assert an random string returned, when valid "client_session_id" and "client_session_id_expiration" are not set.
|
||||
$session->init_session_cookie();
|
||||
$this->assertNotEquals( '', $session->get_client_session_id() );
|
||||
|
||||
$session->init_session_cookie();
|
||||
$_REQUEST['_wc_cart'] = 'test';
|
||||
$this->assertNotEquals( '', $session->get_client_session_id() );
|
||||
|
||||
$session->init_session_cookie();
|
||||
$session->set( 'client_session_id', 'test-client-session-id' );
|
||||
$session->set( 'client_session_id_expiration', '1' );
|
||||
$this->assertNotEquals( 'test-client-session-id', $session->get_client_session_id() );
|
||||
|
||||
// Assert "test-client-session-id" is returned, when valid "client_session_id" and "client_session_id_expiration" are set.
|
||||
$session->init_session_cookie();
|
||||
$session->set( 'client_session_id', 'test-client-session-id' );
|
||||
$session->set( 'client_session_id_expiration', ( time() + 3600 ) );
|
||||
$this->assertEquals( 'test-client-session-id', $session->get_client_session_id() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use function WPGraphQL\WooCommerce\get_includes_directory;
|
||||
use WPGraphQL\WooCommerce\Utils\Transfer_Session_Handler;
|
||||
class TransferSessionHandlerTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
|
||||
/**
|
||||
* Session handler instance.
|
||||
*
|
||||
* @var Transfer_Session_Handler
|
||||
*/
|
||||
private $session;
|
||||
|
||||
public function setUp(): void {
|
||||
// before
|
||||
parent::setUp();
|
||||
|
||||
require_once get_includes_directory() . 'utils/class-transfer-session-handler.php';
|
||||
|
||||
$this->session = new Transfer_Session_Handler();
|
||||
}
|
||||
|
||||
public function testGenerateCustomerId() {
|
||||
// Assert random customer ID is generated when invalid creds are provided.
|
||||
$this->session->init_session_cookie();
|
||||
$this->assertNotEquals( 'test-session-id', $this->session->get_customer_id() );
|
||||
|
||||
$_REQUEST['_wc_cart'] = 'test';
|
||||
$this->session->init_session_cookie();
|
||||
$this->assertNotEquals( 'test-session-id', $this->session->get_customer_id() );
|
||||
|
||||
unset( $_REQUEST['_wc_cart'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$_REQUEST['session_id'] = 'test-session-id';
|
||||
$this->session->init_session_cookie();
|
||||
$this->assertNotEquals( 'test-session-id', $this->session->get_customer_id() );
|
||||
|
||||
// Assert "session_id" is returned, if proper creds are provided.
|
||||
$_REQUEST['_wc_cart'] = 'test';
|
||||
$_REQUEST['session_id'] = 'test-session-id';
|
||||
$this->session->init_session_cookie();
|
||||
$this->assertEquals( 'test-session-id', $this->session->get_customer_id() );
|
||||
}
|
||||
|
||||
public function testGetClientSessionId() {
|
||||
// Assert an empty string is return, when invalid creds are provided.
|
||||
$this->session->init_session_cookie();
|
||||
$this->assertEquals( '', $this->session->get_client_session_id() );
|
||||
|
||||
$this->session->init_session_cookie();
|
||||
$_REQUEST['_wc_cart'] = 'test';
|
||||
$this->assertEquals( '', $this->session->get_client_session_id() );
|
||||
|
||||
$this->session->init_session_cookie();
|
||||
unset( $_REQUEST['_wc_cart'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$_REQUEST['session_id'] = $this->session->get_client_session_id();
|
||||
$this->assertEquals( '', $this->session->get_client_session_id() );
|
||||
|
||||
// Assert "test-client-session-id" is returned, if proper creds are provided.
|
||||
$this->session->init_session_cookie();
|
||||
$this->session->set( 'client_session_id', 'test-client-session-id' );
|
||||
$this->session->set( 'client_session_id_expiration', ( time() + 3600 ) );
|
||||
$_REQUEST['_wc_cart'] = 'test';
|
||||
$_REQUEST['session_id'] = $this->session->get_customer_id();
|
||||
$this->assertEquals( 'test-client-session-id', $this->session->get_client_session_id() );
|
||||
|
||||
// Assert an empty string is returned, because "client_session_id_expiration" is expired.
|
||||
$this->session->init_session_cookie();
|
||||
$this->session->set( 'client_session_id', 'test-client-session-id-2' );
|
||||
$this->session->set( 'client_session_id_expiration', '1' );
|
||||
$_REQUEST['_wc_cart'] = 'test';
|
||||
$_REQUEST['session_id'] = $this->session->get_customer_id();
|
||||
$this->assertEquals( '', $this->session->get_client_session_id() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
class UpdateSessionMutationTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
|
||||
|
||||
public function testUpdateSessionMutation() {
|
||||
// Create registered customer.
|
||||
$registered = $this->factory->customer->create();
|
||||
$this->loginAs( $registered );
|
||||
|
||||
// Create query.
|
||||
$query = '
|
||||
mutation($input: UpdateSessionInput!) {
|
||||
updateSession(input: $input) {
|
||||
session {
|
||||
id
|
||||
key
|
||||
value
|
||||
}
|
||||
customer {
|
||||
id
|
||||
session {
|
||||
id
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$variables = [
|
||||
'input' => [
|
||||
'sessionData' => [
|
||||
[
|
||||
'key' => 'test-2',
|
||||
'value' => 'test-value',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Assert working.
|
||||
*/
|
||||
$response = $this->graphql( compact( 'query', 'variables' ) );
|
||||
$expected = [
|
||||
$this->expectedObject(
|
||||
'updateSession.session.#',
|
||||
[
|
||||
$this->expectedField( 'key', 'test-2' ),
|
||||
$this->expectedField( 'value', 'test-value' ),
|
||||
]
|
||||
),
|
||||
$this->expectedField( 'updateSession.customer.id', $this->toRelayId( 'customer', $registered ) ),
|
||||
$this->expectedObject(
|
||||
'updateSession.customer.session.#',
|
||||
[
|
||||
$this->expectedField( 'key', 'test-2' ),
|
||||
$this->expectedField( 'value', 'test-value' ),
|
||||
]
|
||||
),
|
||||
];
|
||||
|
||||
$this->assertQuerySuccessful( $response, $expected );
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
<?php
|
||||
|
||||
// Turn off "QL_SESSION_HANDLER" for unit tests.
|
||||
//define( 'NO_QL_SESSION_HANDLER', true );
|
||||
|
||||
/**
|
||||
* Remove the "extensions" payload from GraphQL results
|
||||
* so that tests can make assertions without worrying about what's in the extensions payload
|
||||
|
||||
@@ -139,6 +139,17 @@ function init() {
|
||||
}
|
||||
add_action( 'graphql_init', 'WPGraphQL\WooCommerce\init' );
|
||||
|
||||
/**
|
||||
* Initializes Protected Router
|
||||
*/
|
||||
function init_auth_router() {
|
||||
if ( empty( dependencies_not_ready() ) ) {
|
||||
require_once get_includes_directory() . 'class-wp-graphql-woocommerce.php';
|
||||
WP_GraphQL_WooCommerce::load_auth_router();
|
||||
}
|
||||
}
|
||||
add_action( 'plugins_loaded', 'WPGraphQL\WooCommerce\init_auth_router' );
|
||||
|
||||
// Load constants.
|
||||
constants();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user