Completed
Push — master ( fb8708...9f5967 )
by Mike
10:18
created

WC_Auth::auth_endpoint()   C

Complexity

Conditions 16
Paths 25

Size

Total Lines 68
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 68
rs 5.7201
cc 16
eloc 43
nc 25
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 19 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * WooCommerce Auth
4
 *
5
 * Handles wc-auth endpoint requests.
6
 *
7
 * @author   WooThemes
8
 * @category API
9
 * @package  WooCommerce/API
10
 * @since    2.4.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
if ( ! class_exists( 'WC_Auth' ) ) :
18
19
class WC_Auth {
20
21
	/**
22
	 * Version.
23
	 *
24
	 * @var int
25
	 */
26
	const VERSION = 1;
27
28
	/**
29
	 * Setup class.
30
	 *
31
	 * @since 2.4.0
32
	 */
33
	public function __construct() {
34
		// Add query vars
35
		add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
36
37
		// Register auth endpoint
38
		add_action( 'init', array( __CLASS__, 'add_endpoint' ), 0 );
39
40
		// Handle auth requests
41
		add_action( 'parse_request', array( $this, 'handle_auth_requests' ), 0 );
42
	}
43
44
	/**
45
	 * Add query vars.
46
	 *
47
	 * @since  2.4.0
48
	 *
49
	 * @param  array $vars
50
	 *
51
	 * @return string[]
52
	 */
53
	public function add_query_vars( $vars ) {
54
		$vars[] = 'wc-auth-version';
55
		$vars[] = 'wc-auth-route';
56
		return $vars;
57
	}
58
59
	/**
60
	 * Add auth endpoint.
61
	 *
62
	 * @since 2.4.0
63
	 */
64
	public static function add_endpoint() {
65
		add_rewrite_rule( '^wc-auth/v([1]{1})/(.*)?', 'index.php?wc-auth-version=$matches[1]&wc-auth-route=$matches[2]', 'top' );
66
	}
67
68
	/**
69
	 * Get scope name.
70
	 *
71
	 * @since 2.4.0
72
	 *
73
	 * @param  string $scope
74
	 *
75
	 * @return string
76
	 */
77
	protected function get_i18n_scope( $scope ) {
78
		$permissions = array(
79
			'read'       => __( 'Read', 'woocommerce' ),
80
			'write'      => __( 'Write', 'woocommerce' ),
81
			'read_write' => __( 'Read/Write', 'woocommerce' ),
82
		);
83
84
		return $permissions[ $scope ];
85
	}
86
87
	/**
88
	 * Return a list of permissions a scope allows.
89
	 *
90
	 * @since  2.4.0
91
	 *
92
	 * @param  string $scope
93
	 *
94
	 * @return array
95
	 */
96
	protected function get_permissions_in_scope( $scope ) {
97
		$permissions = array();
98
		switch ( $scope )  {
99 View Code Duplication
			case 'read' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
100
				$permissions[] = __( 'View coupons', 'woocommerce' );
101
				$permissions[] = __( 'View customers', 'woocommerce' );
102
				$permissions[] = __( 'View orders and sales reports', 'woocommerce' );
103
				$permissions[] = __( 'View products', 'woocommerce' );
104
			break;
105 View Code Duplication
			case 'write' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
106
				$permissions[] = __( 'Create webhooks', 'woocommerce' );
107
				$permissions[] = __( 'Create coupons', 'woocommerce' );
108
				$permissions[] = __( 'Create customers', 'woocommerce' );
109
				$permissions[] = __( 'Create orders', 'woocommerce' );
110
				$permissions[] = __( 'Create products', 'woocommerce' );
111
			break;
112 View Code Duplication
			case 'read_write' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
113
				$permissions[] = __( 'Create webhooks', 'woocommerce' );
114
				$permissions[] = __( 'View and manage coupons', 'woocommerce' );
115
				$permissions[] = __( 'View and manage customers', 'woocommerce' );
116
				$permissions[] = __( 'View and manage orders and sales reports', 'woocommerce' );
117
				$permissions[] = __( 'View and manage products', 'woocommerce' );
118
			break;
119
		}
120
		return apply_filters( 'woocommerce_api_permissions_in_scope', $permissions, $scope );
121
	}
122
123
	/**
124
	 * Build auth urls.
125
	 *
126
	 * @since  2.4.0
127
	 *
128
	 * @param  array $data
129
	 * @param  string $endpoint
130
	 *
131
	 * @return string
132
	 */
133
	protected function build_url( $data, $endpoint ) {
134
		$url = wc_get_endpoint_url( 'wc-auth/v' . self::VERSION, $endpoint, home_url( '/' ) );
135
136
		return add_query_arg( array(
137
			'app_name'            => wc_clean( $data['app_name'] ),
138
			'user_id'             => wc_clean( $data['user_id'] ),
139
			'return_url'          => urlencode( $this->get_formatted_url( $data['return_url'] ) ),
140
			'callback_url'        => urlencode( $this->get_formatted_url( $data['callback_url'] ) ),
141
			'scope'               => wc_clean( $data['scope'] ),
142
		), $url );
143
	}
144
145
	/**
146
	 * Decode and format a URL.
147
	 * @param  string $url
148
	 * @return array
149
	 */
150
	protected function get_formatted_url( $url ) {
151
		$url = urldecode( $url );
152
153
		if ( ! strstr( $url, '://' ) ) {
154
			$url = 'https://' . $url;
155
		}
156
157
		return $url;
158
	}
159
160
	/**
161
	 * Make validation.
162
	 *
163
	 * @since  2.4.0
164
	 */
165
	protected function make_validation() {
166
		$params = array(
167
			'app_name',
168
			'user_id',
169
			'return_url',
170
			'callback_url',
171
			'scope'
172
		);
173
174
		foreach ( $params as $param ) {
175
			if ( empty( $_REQUEST[ $param ] ) ) {
176
				throw new Exception( sprintf( __( 'Missing parameter %s', 'woocommerce' ), $param ) );
177
			}
178
		}
179
180
		if ( ! in_array( $_REQUEST['scope'], array( 'read', 'write', 'read_write' ) ) ) {
181
			throw new Exception( sprintf( __( 'Invalid scope %s', 'woocommerce' ), wc_clean( $_REQUEST['scope'] ) ) );
182
		}
183
184
		foreach ( array( 'return_url', 'callback_url' ) as $param ) {
185
			$param = $this->get_formatted_url( $_REQUEST[ $param ] );
186
187
			if ( false === filter_var( $param, FILTER_VALIDATE_URL ) ) {
188
				throw new Exception( sprintf( __( 'The %s is not a valid URL', 'woocommerce' ), $param ) );
189
			}
190
		}
191
192
		$callback_url = $this->get_formatted_url( $_REQUEST['callback_url'] );
193
194
		if ( 0 !== stripos( $callback_url, 'https://' ) ) {
195
			throw new Exception( __( 'The callback_url need to be over SSL', 'woocommerce' ) );
196
		}
197
	}
198
199
	/**
200
	 * Create keys.
201
	 *
202
	 * @since  2.4.0
203
	 *
204
	 * @param  string $app_name
205
	 * @param  string $app_user_id
206
	 * @param  string $scope
207
	 *
208
	 * @return array
209
	 */
210
	protected function create_keys( $app_name, $app_user_id, $scope ) {
211
		global $wpdb;
212
213
		$description = sprintf( __( '%s - API %s (created on %s at %s).', 'woocommerce' ), wc_clean( $app_name ), $this->get_i18n_scope( $scope ), date_i18n( wc_date_format() ), date_i18n( wc_time_format() ) );
214
		$user        = wp_get_current_user();
215
216
		// Created API keys.
217
		$permissions     = ( in_array( $scope, array( 'read', 'write', 'read_write' ) ) ) ? sanitize_text_field( $scope ) : 'read';
218
		$consumer_key    = 'ck_' . wc_rand_hash();
219
		$consumer_secret = 'cs_' . wc_rand_hash();
220
221
		$wpdb->insert(
222
			$wpdb->prefix . 'woocommerce_api_keys',
223
			array(
224
				'user_id'         => $user->ID,
225
				'description'     => $description,
226
				'permissions'     => $permissions,
227
				'consumer_key'    => wc_api_hash( $consumer_key ),
228
				'consumer_secret' => $consumer_secret,
229
				'truncated_key'   => substr( $consumer_key, -7 )
230
			),
231
			array(
232
				'%d',
233
				'%s',
234
				'%s',
235
				'%s',
236
				'%s',
237
				'%s'
238
			)
239
		);
240
241
		return array(
242
			'key_id'          => $wpdb->insert_id,
243
			'user_id'         => $app_user_id,
244
			'consumer_key'    => $consumer_key,
245
			'consumer_secret' => $consumer_secret,
246
			'key_permissions' => $permissions
247
		);
248
	}
249
250
	/**
251
	 * Post consumer data.
252
	 *
253
	 * @since  2.4.0
254
	 *
255
	 * @param  array  $consumer_data
256
	 * @param  string $url
257
	 *
258
	 * @return bool
259
	 * @throws Exception
260
	 */
261
	protected function post_consumer_data( $consumer_data, $url ) {
262
		$params = array(
263
			'body'      => json_encode( $consumer_data ),
264
			'timeout'   => 60,
265
			'headers'   => array(
266
				'Content-Type' => 'application/json;charset=' . get_bloginfo( 'charset' ),
267
			)
268
		);
269
270
		$response = wp_safe_remote_post( esc_url_raw( $url ), $params );
271
272
		if ( is_wp_error( $response ) ) {
273
			throw new Exception( $response->get_error_message() );
274
		} else if ( 200 != $response['response']['code'] ) {
275
			throw new Exception( __( 'An error occurred in the request and at the time were unable to send the consumer data', 'woocommerce' ) );
276
		}
277
278
		return true;
279
	}
280
281
	/**
282
	 * Handle auth requests.
283
	 *
284
	 * @since 2.4.0
285
	 */
286
	public function handle_auth_requests() {
287
		global $wp;
288
289
		if ( ! empty( $_GET['wc-auth-version'] ) ) {
290
			$wp->query_vars['wc-auth-version'] = $_GET['wc-auth-version'];
291
		}
292
293
		if ( ! empty( $_GET['wc-auth-route'] ) ) {
294
			$wp->query_vars['wc-auth-route'] = $_GET['wc-auth-route'];
295
		}
296
297
		// wc-auth endpoint requests
298
		if ( ! empty( $wp->query_vars['wc-auth-version'] ) && ! empty( $wp->query_vars['wc-auth-route'] ) ) {
299
			$this->auth_endpoint( $wp->query_vars['wc-auth-route'] );
300
		}
301
	}
302
303
	/**
304
	 * Auth endpoint.
305
	 *
306
	 * @since 2.4.0
307
	 *
308
	 * @param string $route
309
	 */
310
	protected function auth_endpoint( $route ) {
311
		ob_start();
312
313
		$consumer_data = array();
314
315
		try {
316
			if ( 'yes' !== get_option( 'woocommerce_api_enabled' ) ) {
317
				throw new Exception( __( 'API disabled!', 'woocommerce' ) );
318
			}
319
320
			$route = strtolower( wc_clean( $route ) );
321
			$this->make_validation();
322
323
			// Login endpoint
324
			if ( 'login' == $route && ! is_user_logged_in() ) {
325
				wc_get_template( 'auth/form-login.php', array(
326
					'app_name'     => $_REQUEST['app_name'],
327
					'return_url'   => add_query_arg( array( 'success' => 0, 'user_id' => wc_clean( $_REQUEST['user_id'] ) ), $this->get_formatted_url( $_REQUEST['return_url'] ) ),
328
					'redirect_url' => $this->build_url( $_REQUEST, 'authorize' ),
329
				) );
330
331
				exit;
332
333
			// Redirect with user is logged in
334
			} else if ( 'login' == $route && is_user_logged_in() ) {
335
				wp_redirect( esc_url_raw( $this->build_url( $_REQUEST, 'authorize' ) ) );
336
				exit;
337
338
			// Redirect with user is not logged in and trying to access the authorize endpoint
339
			} else if ( 'authorize' == $route && ! is_user_logged_in() ) {
340
				wp_redirect( esc_url_raw( $this->build_url( $_REQUEST, 'login' ) ) );
341
				exit;
342
343
			// Authorize endpoint
344
			} else if ( 'authorize' == $route && current_user_can( 'manage_woocommerce' ) ) {
345
				wc_get_template( 'auth/form-grant-access.php', array(
346
					'app_name'    => $_REQUEST['app_name'],
347
					'return_url'  => add_query_arg( array( 'success' => 0, 'user_id' => wc_clean( $_REQUEST['user_id'] ) ), $this->get_formatted_url( $_REQUEST['return_url'] ) ),
348
					'scope'       => $this->get_i18n_scope( wc_clean( $_REQUEST['scope'] ) ),
1 ignored issue
show
Bug introduced by
It seems like wc_clean($_REQUEST['scope']) targeting wc_clean() can also be of type array; however, WC_Auth::get_i18n_scope() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
349
					'permissions' => $this->get_permissions_in_scope( wc_clean( $_REQUEST['scope'] ) ),
1 ignored issue
show
Bug introduced by
It seems like wc_clean($_REQUEST['scope']) targeting wc_clean() can also be of type array; however, WC_Auth::get_permissions_in_scope() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
350
					'granted_url' => wp_nonce_url( $this->build_url( $_REQUEST, 'access_granted' ), 'wc_auth_grant_access', 'wc_auth_nonce' ),
351
					'logout_url'  => wp_logout_url( $this->build_url( $_REQUEST, 'login' ) ),
352
					'user'        => wp_get_current_user()
353
				) );
354
				exit;
355
356
			// Granted access endpoint
357
			} else if ( 'access_granted' == $route && current_user_can( 'manage_woocommerce' ) ) {
358
				if ( ! isset( $_GET['wc_auth_nonce'] ) || ! wp_verify_nonce( $_GET['wc_auth_nonce'], 'wc_auth_grant_access' ) ) {
359
					throw new Exception( __( 'Invalid nonce verification', 'woocommerce' ) );
360
				}
361
362
				$consumer_data = $this->create_keys( $_REQUEST['app_name'], $_REQUEST['user_id'], $_REQUEST['scope'] );
363
				$response      = $this->post_consumer_data( $consumer_data, $this->get_formatted_url( $_REQUEST['callback_url'] ) );
364
365
				if ( $response ) {
366
					wp_redirect( esc_url_raw( add_query_arg( array( 'success' => 1, 'user_id' => wc_clean( $_REQUEST['user_id'] ) ), $this->get_formatted_url( $_REQUEST['return_url'] ) ) ) );
367
					exit;
368
				}
369
			} else {
370
				throw new Exception( __( 'You do not have permissions to access this page!', 'woocommerce' ) );
371
			}
372
		} catch ( Exception $e ) {
373
			$this->maybe_delete_key( $consumer_data );
374
375
			wp_die( sprintf( __( 'Error: %s', 'woocommerce' ), $e->getMessage() ), __( 'Access Denied', 'woocommerce' ), array( 'response' => 401 ) );
376
		}
377
	}
378
379
	/**
380
	 * Maybe delete key.
381
	 *
382
	 * @since 2.4.0
383
	 *
384
	 * @param array $key
385
	 */
386
	private function maybe_delete_key( $key ) {
387
		global $wpdb;
388
389
		if ( isset( $key['key_id'] ) ) {
390
			$wpdb->delete( $wpdb->prefix . 'woocommerce_api_keys', array( 'key_id' => $key['key_id'] ), array( '%d' ) );
391
		}
392
	}
393
}
394
395
endif;
396
397
return new WC_Auth();
398