Completed
Push — master ( 620bde...3958e3 )
by Gerhard
22s
created

WC_WCCOM_Site::is_request_to_wccom_site_rest_api()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 7
ccs 0
cts 5
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * WooCommerce.com Product Installation.
4
 *
5
 * @package WooCommerce\WooCommerce_Site
6
 * @since   3.7.0
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * WC_WCCOM_Site Class
13
 *
14
 * Main class for WooCommerce.com connected site.
15
 */
16
class WC_WCCOM_Site {
17
18
	/**
19
	 * Load the WCCOM site class.
20
	 *
21
	 * @since 3.7.0
22
	 */
23
	public static function load() {
24
		self::includes();
25
26
		add_action( 'woocommerce_wccom_install_products', array( 'WC_WCCOM_Site_Installer', 'install' ) );
27
		add_filter( 'determine_current_user', array( __CLASS__, 'authenticate_wccom' ), 14 );
28
		add_action( 'woocommerce_rest_api_get_rest_namespaces', array( __CLASS__, 'register_rest_namespace' ) );
29
	}
30
31
	/**
32
	 * Include support files.
33
	 *
34
	 * @since 3.7.0
35
	 */
36
	protected static function includes() {
37
		require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper.php';
38
		require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer.php';
39
	}
40
41
	/**
42
	 * Authenticate WooCommerce.com request.
43
	 *
44
	 * @since 3.7.0
45
	 * @param int|false $user_id User ID.
46
	 * @return int|false
47
	 */
48
	public static function authenticate_wccom( $user_id ) {
49
		if ( ! empty( $user_id ) || ! self::is_request_to_wccom_site_rest_api() ) {
50
			return $user_id;
51
		}
52
53
		$auth_header = self::get_authorization_header();
54
		if ( empty( $auth_header ) ) {
55
			return false;
56
		}
57
58
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
59
		$request_auth = trim( $auth_header );
60
		if ( stripos( $request_auth, 'Bearer ' ) !== 0 ) {
61
			return false;
62
		}
63
64
		if ( empty( $_SERVER['HTTP_X_WOO_SIGNATURE'] ) ) {
65
			return false;
66
		}
67
68
		require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-options.php';
69
		$access_token = trim( substr( $request_auth, 7 ) );
70
		$site_auth    = WC_Helper_Options::get( 'auth' );
71
		if ( empty( $site_auth['access_token'] ) || ! hash_equals( $access_token, $site_auth['access_token'] ) ) {
72
			return false;
73
		}
74
75
		$body      = WP_REST_Server::get_raw_data();
76
		$signature = trim( $_SERVER['HTTP_X_WOO_SIGNATURE'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
77
78
		if ( ! self::verify_wccom_request( $body, $signature, $site_auth['access_token_secret'] ) ) {
79
			return false;
80
		}
81
82
		$user = get_user_by( 'id', $site_auth['user_id'] );
83
		if ( ! $user ) {
84
			return false;
85
		}
86
87
		return $user;
88
	}
89
90
	/**
91
	 * Get the authorization header.
92
	 *
93
	 * On certain systems and configurations, the Authorization header will be
94
	 * stripped out by the server or PHP. Typically this is then used to
95
	 * generate `PHP_AUTH_USER`/`PHP_AUTH_PASS` but not passed on. We use
96
	 * `getallheaders` here to try and grab it out instead.
97
	 *
98
	 * @since 3.7.0
99
	 * @return string Authorization header if set.
100
	 */
101 View Code Duplication
	protected static function get_authorization_header() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
		if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
103
			return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
104
		}
105
106
		if ( function_exists( 'getallheaders' ) ) {
107
			$headers = getallheaders();
108
			// Check for the authoization header case-insensitively.
109
			foreach ( $headers as $key => $value ) {
110
				if ( 'authorization' === strtolower( $key ) ) {
111
					return $value;
112
				}
113
			}
114
		}
115
116
		return '';
117
	}
118
119
	/**
120
	 * Check if this is a request to WCCOM Site REST API.
121
	 *
122
	 * @since 3.7.0
123
	 * @return bool
124
	 */
125
	protected static function is_request_to_wccom_site_rest_api() {
126
		$request_uri = add_query_arg( array() );
127
		$rest_prefix = trailingslashit( rest_get_url_prefix() );
128
		$request_uri = esc_url_raw( wp_unslash( $request_uri ) );
129
130
		return false !== strpos( $request_uri, $rest_prefix . 'wccom-site/' );
131
	}
132
133
	/**
134
	 * Verify WooCommerce.com request from a given body and signature request.
135
	 *
136
	 * @since 3.7.0
137
	 * @param string $body                Request body.
138
	 * @param string $signature           Request signature found in X-Woo-Signature header.
139
	 * @param string $access_token_secret Access token secret for this site.
140
	 * @return bool
141
	 */
142
	protected static function verify_wccom_request( $body, $signature, $access_token_secret ) {
143
		// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
144
		$data = array(
145
			'host'        => $_SERVER['HTTP_HOST'],
146
			'request_uri' => $_SERVER['REQUEST_URI'],
147
			'method'      => strtoupper( $_SERVER['REQUEST_METHOD'] ),
148
		);
149
		// phpcs:enable
150
151
		if ( ! empty( $body ) ) {
152
			$data['body'] = $body;
153
		}
154
155
		$expected_signature = hash_hmac( 'sha256', wp_json_encode( $data ), $access_token_secret );
156
157
		return hash_equals( $expected_signature, $signature );
158
	}
159
160
	/**
161
	 * Register wccom-site REST namespace.
162
	 *
163
	 * @since 3.7.0
164
	 * @param array $namespaces List of registered namespaces.
165
	 * @return array Registered namespaces.
166
	 */
167 1
	public static function register_rest_namespace( $namespaces ) {
168 1
		require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php';
169
170 1
		$namespaces['wccom-site/v1'] = array(
171
			'installer' => 'WC_REST_WCCOM_Site_Installer_Controller',
172
		);
173
174 1
		return $namespaces;
175
	}
176
}
177
178
WC_WCCOM_Site::load();
179