説明なし

class-wc-wccom-site.php 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <?php
  2. /**
  3. * WooCommerce.com Product Installation.
  4. *
  5. * @package WooCommerce\WCCom
  6. * @since 3.7.0
  7. */
  8. defined( 'ABSPATH' ) || exit;
  9. /**
  10. * WC_WCCOM_Site Class
  11. *
  12. * Main class for WooCommerce.com connected site.
  13. */
  14. class WC_WCCOM_Site {
  15. const AUTH_ERROR_FILTER_NAME = 'wccom_auth_error';
  16. /**
  17. * Load the WCCOM site class.
  18. *
  19. * @since 3.7.0
  20. */
  21. public static function load() {
  22. self::includes();
  23. add_action( 'woocommerce_wccom_install_products', array( 'WC_WCCOM_Site_Installer', 'install' ) );
  24. add_filter( 'determine_current_user', array( __CLASS__, 'authenticate_wccom' ), 14 );
  25. add_action( 'woocommerce_rest_api_get_rest_namespaces', array( __CLASS__, 'register_rest_namespace' ) );
  26. }
  27. /**
  28. * Include support files.
  29. *
  30. * @since 3.7.0
  31. */
  32. protected static function includes() {
  33. require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper.php';
  34. require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer.php';
  35. require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer-requirements-check.php';
  36. }
  37. /**
  38. * Authenticate WooCommerce.com request.
  39. *
  40. * @since 3.7.0
  41. * @param int|false $user_id User ID.
  42. * @return int|false
  43. */
  44. public static function authenticate_wccom( $user_id ) {
  45. if ( ! empty( $user_id ) || ! self::is_request_to_wccom_site_rest_api() ) {
  46. return $user_id;
  47. }
  48. $auth_header = trim( self::get_authorization_header() );
  49. if ( stripos( $auth_header, 'Bearer ' ) === 0 ) {
  50. $access_token = trim( substr( $auth_header, 7 ) );
  51. } elseif ( ! empty( $_GET['token'] ) && is_string( $_GET['token'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  52. $access_token = trim( $_GET['token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  53. } else {
  54. add_filter(
  55. self::AUTH_ERROR_FILTER_NAME,
  56. function() {
  57. return new WP_Error(
  58. WC_REST_WCCOM_Site_Installer_Errors::NO_ACCESS_TOKEN_CODE,
  59. WC_REST_WCCOM_Site_Installer_Errors::NO_ACCESS_TOKEN_MESSAGE,
  60. array( 'status' => WC_REST_WCCOM_Site_Installer_Errors::NO_ACCESS_TOKEN_HTTP_CODE )
  61. );
  62. }
  63. );
  64. return false;
  65. }
  66. if ( ! empty( $_SERVER['HTTP_X_WOO_SIGNATURE'] ) ) {
  67. $signature = trim( $_SERVER['HTTP_X_WOO_SIGNATURE'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  68. } elseif ( ! empty( $_GET['signature'] ) && is_string( $_GET['signature'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  69. $signature = trim( $_GET['signature'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  70. } else {
  71. add_filter(
  72. self::AUTH_ERROR_FILTER_NAME,
  73. function() {
  74. return new WP_Error(
  75. WC_REST_WCCOM_Site_Installer_Errors::NO_SIGNATURE_CODE,
  76. WC_REST_WCCOM_Site_Installer_Errors::NO_SIGNATURE_MESSAGE,
  77. array( 'status' => WC_REST_WCCOM_Site_Installer_Errors::NO_SIGNATURE_HTTP_CODE )
  78. );
  79. }
  80. );
  81. return false;
  82. }
  83. require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-options.php';
  84. $site_auth = WC_Helper_Options::get( 'auth' );
  85. if ( empty( $site_auth['access_token'] ) ) {
  86. add_filter(
  87. self::AUTH_ERROR_FILTER_NAME,
  88. function() {
  89. return new WP_Error(
  90. WC_REST_WCCOM_Site_Installer_Errors::SITE_NOT_CONNECTED_CODE,
  91. WC_REST_WCCOM_Site_Installer_Errors::SITE_NOT_CONNECTED_MESSAGE,
  92. array( 'status' => WC_REST_WCCOM_Site_Installer_Errors::SITE_NOT_CONNECTED_HTTP_CODE )
  93. );
  94. }
  95. );
  96. return false;
  97. }
  98. if ( ! hash_equals( $access_token, $site_auth['access_token'] ) ) {
  99. add_filter(
  100. self::AUTH_ERROR_FILTER_NAME,
  101. function() {
  102. return new WP_Error(
  103. WC_REST_WCCOM_Site_Installer_Errors::INVALID_TOKEN_CODE,
  104. WC_REST_WCCOM_Site_Installer_Errors::INVALID_TOKEN_MESSAGE,
  105. array( 'status' => WC_REST_WCCOM_Site_Installer_Errors::INVALID_TOKEN_HTTP_CODE )
  106. );
  107. }
  108. );
  109. return false;
  110. }
  111. $body = WP_REST_Server::get_raw_data();
  112. if ( ! self::verify_wccom_request( $body, $signature, $site_auth['access_token_secret'] ) ) {
  113. add_filter(
  114. self::AUTH_ERROR_FILTER_NAME,
  115. function() {
  116. return new WP_Error(
  117. WC_REST_WCCOM_Site_Installer_Errors::REQUEST_VERIFICATION_FAILED_CODE,
  118. WC_REST_WCCOM_Site_Installer_Errors::REQUEST_VERIFICATION_FAILED_MESSAGE,
  119. array( 'status' => WC_REST_WCCOM_Site_Installer_Errors::REQUEST_VERIFICATION_FAILED_HTTP_CODE )
  120. );
  121. }
  122. );
  123. return false;
  124. }
  125. $user = get_user_by( 'id', $site_auth['user_id'] );
  126. if ( ! $user ) {
  127. add_filter(
  128. self::AUTH_ERROR_FILTER_NAME,
  129. function() {
  130. return new WP_Error(
  131. WC_REST_WCCOM_Site_Installer_Errors::USER_NOT_FOUND_CODE,
  132. WC_REST_WCCOM_Site_Installer_Errors::USER_NOT_FOUND_MESSAGE,
  133. array( 'status' => WC_REST_WCCOM_Site_Installer_Errors::USER_NOT_FOUND_HTTP_CODE )
  134. );
  135. }
  136. );
  137. return false;
  138. }
  139. return $user;
  140. }
  141. /**
  142. * Get the authorization header.
  143. *
  144. * On certain systems and configurations, the Authorization header will be
  145. * stripped out by the server or PHP. Typically this is then used to
  146. * generate `PHP_AUTH_USER`/`PHP_AUTH_PASS` but not passed on. We use
  147. * `getallheaders` here to try and grab it out instead.
  148. *
  149. * @since 3.7.0
  150. * @return string Authorization header if set.
  151. */
  152. protected static function get_authorization_header() {
  153. if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
  154. return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  155. }
  156. if ( function_exists( 'getallheaders' ) ) {
  157. $headers = getallheaders();
  158. // Check for the authoization header case-insensitively.
  159. foreach ( $headers as $key => $value ) {
  160. if ( 'authorization' === strtolower( $key ) ) {
  161. return $value;
  162. }
  163. }
  164. }
  165. return '';
  166. }
  167. /**
  168. * Check if this is a request to WCCOM Site REST API.
  169. *
  170. * @since 3.7.0
  171. * @return bool
  172. */
  173. protected static function is_request_to_wccom_site_rest_api() {
  174. if ( isset( $_REQUEST['rest_route'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  175. $route = wp_unslash( $_REQUEST['rest_route'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
  176. $rest_prefix = '';
  177. } else {
  178. $route = wp_unslash( add_query_arg( array() ) );
  179. $rest_prefix = trailingslashit( rest_get_url_prefix() );
  180. }
  181. return false !== strpos( $route, $rest_prefix . 'wccom-site/' );
  182. }
  183. /**
  184. * Verify WooCommerce.com request from a given body and signature request.
  185. *
  186. * @since 3.7.0
  187. * @param string $body Request body.
  188. * @param string $signature Request signature found in X-Woo-Signature header.
  189. * @param string $access_token_secret Access token secret for this site.
  190. * @return bool
  191. */
  192. protected static function verify_wccom_request( $body, $signature, $access_token_secret ) {
  193. // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  194. $data = array(
  195. 'host' => $_SERVER['HTTP_HOST'],
  196. 'request_uri' => urldecode( remove_query_arg( array( 'token', 'signature' ), $_SERVER['REQUEST_URI'] ) ),
  197. 'method' => strtoupper( $_SERVER['REQUEST_METHOD'] ),
  198. );
  199. // phpcs:enable
  200. if ( ! empty( $body ) ) {
  201. $data['body'] = $body;
  202. }
  203. $expected_signature = hash_hmac( 'sha256', wp_json_encode( $data ), $access_token_secret );
  204. return hash_equals( $expected_signature, $signature );
  205. }
  206. /**
  207. * Register wccom-site REST namespace.
  208. *
  209. * @since 3.7.0
  210. * @param array $namespaces List of registered namespaces.
  211. * @return array Registered namespaces.
  212. */
  213. public static function register_rest_namespace( $namespaces ) {
  214. require_once WC_ABSPATH . 'includes/wccom-site/rest-api/class-wc-rest-wccom-site-installer-errors.php';
  215. require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php';
  216. $namespaces['wccom-site/v1'] = array(
  217. 'installer' => 'WC_REST_WCCOM_Site_Installer_Controller',
  218. );
  219. return $namespaces;
  220. }
  221. }
  222. WC_WCCOM_Site::load();