1 <?php
2 3 4 5 6 7 8 9
10
11 if ( ! defined( 'ABSPATH' ) ) exit;
12
13 class WC_API_Authentication {
14
15 16 17 18 19 20
21 public function __construct() {
22
23
24 add_filter( 'woocommerce_api_check_authentication', array( $this, 'authenticate' ), 0 );
25 }
26
27 28 29 30 31 32 33
34 public function authenticate( $user ) {
35
36
37 if ( '/' === WC()->api->server->path )
38 return new WP_User(0);
39
40 try {
41
42 if ( is_ssl() )
43 $user = $this->perform_ssl_authentication();
44 else
45 $user = $this->perform_oauth_authentication();
46
47
48 $this->check_api_key_permissions( $user );
49
50 } catch ( Exception $e ) {
51
52 $user = new WP_Error( 'woocommerce_api_authentication_error', $e->getMessage(), array( 'status' => $e->getCode() ) );
53 }
54
55 return $user;
56 }
57
58 59 60 61 62 63 64 65 66 67
68 private function perform_ssl_authentication() {
69
70 $params = WC()->api->server->params['GET'];
71
72
73 if ( ! empty( $_SERVER['PHP_AUTH_USER'] ) ) {
74
75
76 $consumer_key = $_SERVER['PHP_AUTH_USER'];
77
78 } elseif ( ! empty( $params['consumer_key'] ) ) {
79
80
81 $consumer_key = $params['consumer_key'];
82
83 } else {
84
85 throw new Exception( __( 'Consumer Key is missing', 'woocommerce' ), 404 );
86 }
87
88
89 if ( ! empty( $_SERVER['PHP_AUTH_PW'] ) ) {
90
91
92 $consumer_secret = $_SERVER['PHP_AUTH_PW'];
93
94 } elseif ( ! empty( $params['consumer_secret'] ) ) {
95
96
97 $consumer_secret = $params['consumer_secret'];
98
99 } else {
100
101 throw new Exception( __( 'Consumer Secret is missing', 'woocommerce' ), 404 );
102 }
103
104 $user = $this->get_user_by_consumer_key( $consumer_key );
105
106 if ( ! $this->is_consumer_secret_valid( $user, $consumer_secret ) ) {
107 throw new Exception( __( 'Consumer Secret is invalid', 'woocommerce' ), 401 );
108 }
109
110 return $user;
111 }
112
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
130 private function perform_oauth_authentication() {
131
132 $params = WC()->api->server->params['GET'];
133
134 $param_names = array( 'oauth_consumer_key', 'oauth_timestamp', 'oauth_nonce', 'oauth_signature', 'oauth_signature_method' );
135
136
137 foreach ( $param_names as $param_name ) {
138
139 if ( empty( $params[ $param_name ] ) )
140 throw new Exception( sprintf( __( '%s parameter is missing', 'woocommerce' ), $param_name ), 404 );
141 }
142
143
144 $user = $this->get_user_by_consumer_key( $params['oauth_consumer_key'] );
145
146
147 $this->check_oauth_signature( $user, $params );
148 $this->check_oauth_timestamp_and_nonce( $user, $params['oauth_timestamp'], $params['oauth_nonce'] );
149
150
151 return $user;
152 }
153
154 155 156 157 158 159 160 161
162 private function get_user_by_consumer_key( $consumer_key ) {
163
164 $user_query = new WP_User_Query(
165 array(
166 'meta_key' => 'woocommerce_api_consumer_key',
167 'meta_value' => $consumer_key,
168 )
169 );
170
171 $users = $user_query->get_results();
172
173 if ( empty( $users[0] ) )
174 throw new Exception( __( 'Consumer Key is invalid', 'woocommerce' ), 401 );
175
176 return $users[0];
177 }
178
179 180 181 182 183 184 185 186
187 private function is_consumer_secret_valid( WP_User $user, $consumer_secret ) {
188
189 return $user->woocommerce_api_consumer_secret === $consumer_secret;
190 }
191
192 193 194 195 196 197 198 199
200 private function check_oauth_signature( $user, $params ) {
201
202 $http_method = strtoupper( WC()->api->server->method );
203
204 $base_request_uri = rawurlencode( untrailingslashit( get_woocommerce_api_url( '' ) ) . WC()->api->server->path );
205
206
207 $consumer_signature = rawurldecode( $params['oauth_signature'] );
208 unset( $params['oauth_signature'] );
209
210
211 if ( isset( $params['filter'] ) ) {
212 $filters = $params['filter'];
213 unset( $params['filter'] );
214 foreach ( $filters as $filter => $filter_value ) {
215 $params['filter[' . $filter . ']'] = $filter_value;
216 }
217 }
218
219
220 $params = $this->normalize_parameters( $params );
221
222
223 if ( ! uksort( $params, 'strcmp' ) ) {
224 throw new Exception( __( 'Invalid Signature - failed to sort parameters', 'woocommerce' ), 401 );
225 }
226
227
228 $query_params = array();
229 foreach ( $params as $param_key => $param_value ) {
230
231 $query_params[] = $param_key . '%3D' . $param_value;
232 }
233 $query_string = implode( '%26', $query_params );
234
235 $string_to_sign = $http_method . '&' . $base_request_uri . '&' . $query_string;
236
237 if ( $params['oauth_signature_method'] !== 'HMAC-SHA1' && $params['oauth_signature_method'] !== 'HMAC-SHA256' ) {
238 throw new Exception( __( 'Invalid Signature - signature method is invalid', 'woocommerce' ), 401 );
239 }
240
241 $hash_algorithm = strtolower( str_replace( 'HMAC-', '', $params['oauth_signature_method'] ) );
242
243 $signature = base64_encode( hash_hmac( $hash_algorithm, $string_to_sign, $user->woocommerce_api_consumer_secret, true ) );
244
245 if ( $signature !== $consumer_signature ) {
246 throw new Exception( __( 'Invalid Signature - provided signature does not match', 'woocommerce' ), 401 );
247 }
248 }
249
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
270 private function normalize_parameters( $parameters ) {
271
272 $normalized_parameters = array();
273
274 foreach ( $parameters as $key => $value ) {
275
276
277 $key = str_replace( '%', '%25', rawurlencode( rawurldecode( $key ) ) );
278 $value = str_replace( '%', '%25', rawurlencode( rawurldecode( $value ) ) );
279
280 $normalized_parameters[ $key ] = $value;
281 }
282
283 return $normalized_parameters;
284 }
285
286 287 288 289 290 291 292 293 294 295 296 297
298 private function check_oauth_timestamp_and_nonce( $user, $timestamp, $nonce ) {
299
300 $valid_window = 15 * 60;
301
302 if ( ( $timestamp < time() - $valid_window ) || ( $timestamp > time() + $valid_window ) )
303 throw new Exception( __( 'Invalid timestamp', 'woocommerce' ) );
304
305 $used_nonces = $user->woocommerce_api_nonces;
306
307 if ( empty( $used_nonces ) )
308 $used_nonces = array();
309
310 if ( in_array( $nonce, $used_nonces ) )
311 throw new Exception( __( 'Invalid nonce - nonce has already been used', 'woocommerce' ), 401 );
312
313 $used_nonces[ $timestamp ] = $nonce;
314
315
316 foreach( $used_nonces as $nonce_timestamp => $nonce ) {
317
318 if ( $nonce_timestamp < ( time() - $valid_window ) )
319 unset( $used_nonces[ $nonce_timestamp ] );
320 }
321
322 update_user_meta( $user->ID, 'woocommerce_api_nonces', $used_nonces );
323 }
324
325 326 327 328 329 330
331 public function check_api_key_permissions( $user ) {
332
333 $key_permissions = $user->woocommerce_api_key_permissions;
334
335 switch ( WC()->api->server->method ) {
336
337 case 'HEAD':
338 case 'GET':
339 if ( 'read' !== $key_permissions && 'read_write' !== $key_permissions ) {
340 throw new Exception( __( 'The API key provided does not have read permissions', 'woocommerce' ), 401 );
341 }
342 break;
343
344 case 'POST':
345 case 'PUT':
346 case 'PATCH':
347 case 'DELETE':
348 if ( 'write' !== $key_permissions && 'read_write' !== $key_permissions ) {
349 throw new Exception( __( 'The API key provided does not have write permissions', 'woocommerce' ), 401 );
350 }
351 break;
352 }
353 }
354 }
355