Completed
Push — update/build-script-to-yarn ( 578122...3d1d20 )
by
unknown
22:38 queued 15:50
created

Jetpack_Signature   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 254
Duplicated Lines 2.36 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 6
loc 254
rs 3.2
c 0
b 0
f 0
wmc 65
lcom 1
cbo 1

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
F sign_current_request() 0 85 27
F sign_request() 6 112 29
A normalized_query_parameters() 0 19 2
A encode_3986() 0 8 2
A join_with_equal_sign() 0 10 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Signature often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Signature, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
// These constants can be set in wp-config.php to ensure sites behind proxies will still work.
4
// Setting these constants, though, is *not* the preferred method. It's better to configure
5
// the proxy to send the X-Forwarded-Port header.
6
defined( 'JETPACK_SIGNATURE__HTTP_PORT'  ) or define( 'JETPACK_SIGNATURE__HTTP_PORT' , 80  );
7
defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) or define( 'JETPACK_SIGNATURE__HTTPS_PORT', 443 );
8
9
class Jetpack_Signature {
10
	public $token;
11
	public $secret;
12
13
	function __construct( $access_token, $time_diff = 0 ) {
14
		$secret = explode( '.', $access_token );
15
		if ( 2 != count( $secret ) )
16
			return;
17
18
		$this->token  = $secret[0];
19
		$this->secret = $secret[1];
20
		$this->time_diff = $time_diff;
0 ignored issues
show
Bug introduced by
The property time_diff does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
21
	}
22
23
	function sign_current_request( $override = array() ) {
24
		if ( isset( $override['scheme'] ) ) {
25
			$scheme = $override['scheme'];
26
			if ( !in_array( $scheme, array( 'http', 'https' ) ) ) {
27
				return new Jetpack_Error( 'invalid_scheme', 'Invalid URL scheme' );
28
			}
29
		} else {
30
			if ( is_ssl() ) {
31
				$scheme = 'https';
32
			} else {
33
				$scheme = 'http';
34
			}
35
		}
36
37
		$host_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $_SERVER['SERVER_PORT'];
38
39
		/**
40
		 * Note: This port logic is tested in the Jetpack_Cxn_Tests->test__server_port_value() test.
41
		 * Please update the test if any changes are made in this logic.
42
		 */
43
		if ( is_ssl() ) {
44
			// 443: Standard Port
45
			// 80: Assume we're behind a proxy without X-Forwarded-Port. Hardcoding "80" here means most sites
46
			//     with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with
47
			//     the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a
48
			//     site at https://example.com:80/ (which would be a strange configuration).
49
			// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
50
			//                                if the site is behind a proxy running on port 443 without
51
			//                                X-Forwarded-Port and the back end's port is *not* 80. It's better,
52
			//                                though, to configure the proxy to send X-Forwarded-Port.
53
			$port = in_array( $host_port, array( 443, 80, JETPACK_SIGNATURE__HTTPS_PORT ) ) ? '' : $host_port;
54
		} else {
55
			// 80: Standard Port
56
			// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
57
			//                                if the site is behind a proxy running on port 80 without
58
			//                                X-Forwarded-Port. It's better, though, to configure the proxy to
59
			//                                send X-Forwarded-Port.
60
			$port = in_array( $host_port, array( 80, JETPACK_SIGNATURE__HTTP_PORT ) ) ? '' : $host_port;
61
		}
62
63
		$url = "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . stripslashes( $_SERVER['REQUEST_URI'] );
64
65
		if ( array_key_exists( 'body', $override ) && ! empty( $override['body'] ) ) {
66
			$body = $override['body'];
67
		} else if ( 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
68
			$body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null;
69
70
			// Convert the $_POST to the body, if the body was empty. This is how arrays are hashed
71
			// and encoded on the Jetpack side.
72
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
73
				if ( empty( $body ) && is_array( $_POST ) && count( $_POST ) > 0 ) {
74
					$body = $_POST;
75
				}
76
			}
77
		} else if ( 'PUT' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
78
			// This is a little strange-looking, but there doesn't seem to be another way to get the PUT body
79
			$raw_put_data = file_get_contents( 'php://input' );
80
			parse_str( $raw_put_data, $body );
81
82
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
83
				$put_data = json_decode( $raw_put_data, true );
84
				if ( is_array( $put_data ) && count( $put_data ) > 0 ) {
85
					$body = $put_data;
86
				}
87
			}
88
		} else {
89
			$body = null;
90
		}
91
92
		if ( empty( $body ) ) {
93
			$body = null;
94
		}
95
96
		$a = array();
97
		foreach ( array( 'token', 'timestamp', 'nonce', 'body-hash' ) as $parameter ) {
98
			if ( isset( $override[$parameter] ) ) {
99
				$a[$parameter] = $override[$parameter];
100
			} else {
101
				$a[$parameter] = isset( $_GET[$parameter] ) ? stripslashes( $_GET[$parameter] ) : '';
102
			}
103
		}
104
105
		$method = isset( $override['method'] ) ? $override['method'] : $_SERVER['REQUEST_METHOD'];
106
		return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $method, $url, $body, true );
107
	}
108
109
	// body_hash v. body-hash is annoying.  Refactor to accept an array?
110
	function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '', $method = '', $url = '', $body = null, $verify_body_hash = true ) {
111
		if ( !$this->secret ) {
112
			return new Jetpack_Error( 'invalid_secret', 'Invalid secret' );
113
		}
114
115
		if ( !$this->token ) {
116
			return new Jetpack_Error( 'invalid_token', 'Invalid token' );
117
		}
118
119
		list( $token ) = explode( '.', $token );
120
121
		if ( 0 !== strpos( $token, "$this->token:" ) ) {
122
			return new Jetpack_Error( 'token_mismatch', 'Incorrect token' );
123
		}
124
125
		// If we got an array at this point, let's encode it, so we can see what it looks like as a string.
126
		if ( is_array( $body ) ) {
127
			if ( count( $body ) > 0 ) {
128
				$body = json_encode( $body );
129
130
			} else {
131
				$body = '';
132
			}
133
		}
134
135
		$required_parameters = array( 'token', 'timestamp', 'nonce', 'method', 'url' );
136
		if ( !is_null( $body ) ) {
137
			$required_parameters[] = 'body_hash';
138
			if ( !is_string( $body ) ) {
139
				return new Jetpack_Error( 'invalid_body', 'Body is malformed.' );
140
			}
141
		}
142
143
		foreach ( $required_parameters as $required ) {
144 View Code Duplication
			if ( !is_scalar( $$required ) ) {
145
				return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', str_replace( '_', '-', $required ) ) );
146
			}
147
148 View Code Duplication
			if ( !strlen( $$required ) ) {
149
				return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is missing.', str_replace( '_', '-', $required ) ) );
150
			}
151
		}
152
153
		if ( empty( $body ) ) {
154
			if ( $body_hash ) {
155
				return new Jetpack_Error( 'invalid_body_hash', 'The body hash does not match.' );
156
			}
157
		} else {
158
			if ( $verify_body_hash && jetpack_sha1_base64( $body ) !== $body_hash ) {
159
				return new Jetpack_Error( 'invalid_body_hash', 'The body hash does not match.' );
160
			}
161
		}
162
163
		$parsed = parse_url( $url );
164
		if ( !isset( $parsed['host'] ) ) {
165
			return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'url' ) );
166
		}
167
168
		if ( !empty( $parsed['port'] ) ) {
169
			$port = $parsed['port'];
170
		} else {
171
			if ( 'http' == $parsed['scheme'] ) {
172
				$port = 80;
173
			} else if ( 'https' == $parsed['scheme'] ) {
174
				$port = 443;
175
			} else {
176
				return new Jetpack_Error( 'unknown_scheme_port', "The scheme's port is unknown" );
177
			}
178
		}
179
180
		if ( !ctype_digit( "$timestamp" ) || 10 < strlen( $timestamp ) ) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug.
181
			return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'timestamp' ) );
182
		}
183
184
		$local_time = $timestamp - $this->time_diff;
185
		if ( $local_time < time() - 600 || $local_time > time() + 300 ) {
186
			return new Jetpack_Error( 'invalid_signature', 'The timestamp is too old.' );
187
		}
188
189
		if ( 12 < strlen( $nonce ) || preg_match( '/[^a-zA-Z0-9]/', $nonce ) ) {
190
			return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'nonce' ) );
191
		}
192
193
		$normalized_request_pieces = array(
194
			$token,
195
			$timestamp,
196
			$nonce,
197
			$body_hash,
198
			strtoupper( $method ),
199
			strtolower( $parsed['host'] ),
200
			$port,
201
			$parsed['path'],
202
			// Normalized Query String
203
		);
204
205
		$normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) );
206
		$flat_normalized_request_pieces = array();
207
		foreach ($normalized_request_pieces as $piece) {
208
			if ( is_array( $piece ) ) {
209
				foreach ( $piece as $subpiece ) {
210
					$flat_normalized_request_pieces[] = $subpiece;
211
				}
212
			} else {
213
				$flat_normalized_request_pieces[] = $piece;
214
			}
215
		}
216
		$normalized_request_pieces = $flat_normalized_request_pieces;
217
218
		$normalized_request_string = join( "\n", $normalized_request_pieces ) . "\n";
219
220
		return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $this->secret, true ) );
221
	}
222
223
	function normalized_query_parameters( $query_string ) {
224
		parse_str( $query_string, $array );
225
		if ( get_magic_quotes_gpc() )
226
			$array = stripslashes_deep( $array );
227
228
		unset( $array['signature'] );
229
230
		$names  = array_keys( $array );
231
		$values = array_values( $array );
232
233
		$names  = array_map( array( $this, 'encode_3986' ), $names  );
234
		$values = array_map( array( $this, 'encode_3986' ), $values );
235
236
		$pairs  = array_map( array( $this, 'join_with_equal_sign' ), $names, $values );
237
238
		sort( $pairs );
239
240
		return $pairs;
241
	}
242
243
	function encode_3986( $string_or_array ) {
244
		if ( is_array( $string_or_array ) ) {
245
			return array_map( array( $this, 'encode_3986' ), $string_or_array );
246
		}
247
248
		$string_or_array = rawurlencode( $string_or_array );
249
		return str_replace( '%7E', '~', $string_or_array ); // prior to PHP 5.3, rawurlencode was RFC 1738
250
	}
251
252
	function join_with_equal_sign( $name, $value ) {
253
		if ( is_array( $value ) ) {
254
			$result = array();
255
			foreach ( $value as $array_key => $array_value ) {
256
				$result[] = $name . '[' . $array_key . ']' . '=' . $array_value;
257
			}
258
			return $result;
259
		}
260
		return "{$name}={$value}";
261
	}
262
}
263
264
function jetpack_sha1_base64( $text ) {
265
	return base64_encode( sha1( $text, true ) );
266
}
267