Completed
Push — update/exit-after-redirect ( 1f3555...e50868 )
by
unknown
47:59 queued 38:19
created

class.jetpack-client-server.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * Client = Plugin
5
 * Client Server = API Methods the Plugin must respond to
6
 */
7
class Jetpack_Client_Server {
8
9
	/**
10
	 * Authorizations
11
	 */
12
	function client_authorize() {
13
		$data              = stripslashes_deep( $_GET );
14
		$data['auth_type'] = 'client';
15
		$role              = Jetpack::translate_current_user_to_role();
16
		$redirect          = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
17
18
		check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" );
19
20
		$result = $this->authorize( $data );
21
		if ( is_wp_error( $result ) ) {
22
			Jetpack::state( 'error', $result->get_error_code() );
23
		}
24
25
		if ( wp_validate_redirect( $redirect ) ) {
26
			wp_safe_redirect( $redirect );
27
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method client_authorize() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
28
		} else {
29
			wp_safe_redirect( Jetpack::admin_url() );
30
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method client_authorize() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
31
		}
32
33
		/**
34
		 * Fires after the Jetpack client is authorized to communicate with WordPress.com.
35
		 *
36
		 * @since 4.2.0
37
		 *
38
		 * @param int Jetpack Blog ID.
39
		 */
40
		do_action( 'jetpack_client_authorized', Jetpack_Options::get_option( 'id' ) );
0 ignored issues
show
/** * Fires after the J...ons::get_option('id')); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
41
42
		$this->do_exit();
43
	}
44
45
	function authorize( $data = array() ) {
46
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
47
48
		$jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
49
		// Checking if site has been active/connected previously before recording unique connection
50
		if ( ! $jetpack_unique_connection ) {
51
			// jetpack_unique_connection option has never been set
52
			$jetpack_unique_connection = array(
53
				'connected'     => 0,
54
				'disconnected'  => 0,
55
				'version'       => '3.6.1',
56
			);
57
58
			update_option( 'jetpack_unique_connection', $jetpack_unique_connection );
59
60
			//track unique connection
61
			$jetpack = $this->get_jetpack();;
62
63
			$jetpack->stat( 'connections', 'unique-connection' );
64
			$jetpack->do_stats( 'server_side' );
65
		}
66
67
		// increment number of times connected
68
		$jetpack_unique_connection['connected'] += 1;
69
		Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
70
71
		$role = Jetpack::translate_current_user_to_role();
72
73
		if ( ! $role ) {
74
			return new Jetpack_Error( 'no_role', 'Invalid request.', 400 );
75
		}
76
77
		$cap = Jetpack::translate_role_to_cap( $role );
78
		if ( ! $cap ) {
79
			return new Jetpack_Error( 'no_cap', 'Invalid request.', 400 );
80
		}
81
82
		if ( ! empty( $data['error'] ) ) {
83
			return new Jetpack_Error( $data['error'], 'Error included in the request.', 400 );
84
		}
85
86
		if ( ! isset( $data['state'] ) ) {
87
			return new Jetpack_Error( 'no_state', 'Request must include state.', 400 );
88
		}
89
90
		if ( ! ctype_digit( $data['state'] ) ) {
91
			return new Jetpack_Error( $data['error'], 'State must be an integer.', 400 );
92
		}
93
94
		$current_user_id = get_current_user_id();
95
		if ( $current_user_id != $data['state'] ) {
96
			return new Jetpack_Error( 'wrong_state', 'State does not match current user.', 400 );
97
		}
98
99
		if ( empty( $data['code'] ) ) {
100
			return new Jetpack_Error( 'no_code', 'Request must include an authorization code.', 400 );
101
		}
102
103
		$token = $this->get_token( $data );
104
105
		if ( is_wp_error( $token ) ) {
106
			$code = $token->get_error_code();
107
			if ( empty( $code ) ) {
108
				$code = 'invalid_token';
109
			}
110
			return new Jetpack_Error( $code, $token->get_error_message(), 400 );
111
		}
112
113
		if ( ! $token ) {
114
			return new Jetpack_Error( 'no_token', 'Error generating token.', 400 );
115
		}
116
117
		$is_master_user = ! Jetpack::is_active();
118
119
		Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_master_user );
120
121
		if ( ! $is_master_user ) {
122
			Jetpack::state( 'message', 'linked' );
123
			// Don't activate anything since we are just connecting a user.
124
			return 'linked';
125
		}
126
127
		$redirect_on_activation_error = ( 'client' === $data['auth_type'] ) ? true : false;
128 View Code Duplication
		if ( $active_modules = Jetpack_Options::get_option( 'active_modules' ) ) {
129
			Jetpack::delete_active_modules();
130
131
			Jetpack::activate_default_modules( 999, 1, $active_modules, $redirect_on_activation_error, false );
132
		} else {
133
			Jetpack::activate_default_modules( false, false, array(), $redirect_on_activation_error, false );
134
		}
135
136
		// If redirect_uri is SSO, ensure SSO module is enabled
137
		parse_str( parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
138
		/** This filter is documented in class.jetpack-cli.php */
139
		if ( isset( $redirect_options['action'] ) && 'jetpack-sso' === $redirect_options['action'] && apply_filters( 'jetpack_start_enable_sso', true ) ) {
140
			Jetpack::activate_module( 'sso', false, false );
141
		}
142
143
		// Since this is a fresh connection, be sure to clear out IDC options
144
		Jetpack_IDC::clear_all_idc_options();
145
146
		// Start nonce cleaner
147
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
148
		wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
149
150
		Jetpack::state( 'message', 'authorized' );
151
		return 'authorized';
152
	}
153
154
	public static function deactivate_plugin( $probable_file, $probable_title ) {
155
		include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
156
		if ( is_plugin_active( $probable_file ) ) {
157
			deactivate_plugins( $probable_file );
158
			return 1;
159
		} else {
160
			// If the plugin is not in the usual place, try looking through all active plugins.
161
			$active_plugins = Jetpack::get_active_plugins();
162
			foreach ( $active_plugins as $plugin ) {
163
				$data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
164
				if ( $data['Name'] == $probable_title ) {
165
					deactivate_plugins( $plugin );
166
					return 1;
167
				}
168
			}
169
		}
170
171
		return 0;
172
	}
173
174
	/**
175
	 * @return object|WP_Error
176
	 */
177
	function get_token( $data ) {
178
		$role = Jetpack::translate_current_user_to_role();
179
180
		if ( ! $role ) {
181
			return new Jetpack_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) );
182
		}
183
184
		$client_secret = Jetpack_Data::get_access_token();
185
		if ( ! $client_secret ) {
186
			return new Jetpack_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) );
187
		}
188
189
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
190
		$redirect_uri = ( 'calypso' === $data['auth_type'] )
191
			? $data['redirect_uri']
192
			: add_query_arg( array(
193
				'action' => 'authorize',
194
				'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
195
				'redirect' => $redirect ? urlencode( $redirect ) : false,
196
			), menu_page_url( 'jetpack', false ) );
197
198
		$body = array(
199
			'client_id' => Jetpack_Options::get_option( 'id' ),
200
			'client_secret' => $client_secret->secret,
201
			'grant_type' => 'authorization_code',
202
			'code' => $data['code'],
203
			'redirect_uri' => $redirect_uri,
204
		);
205
206
		$args = array(
207
			'method' => 'POST',
208
			'body' => $body,
209
			'headers' => array(
210
				'Accept' => 'application/json',
211
			),
212
		);
213
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'token' ) ), $args );
214
215
		if ( is_wp_error( $response ) ) {
216
			return new Jetpack_Error( 'token_http_request_failed', $response->get_error_message() );
217
		}
218
219
		$code = wp_remote_retrieve_response_code( $response );
220
		$entity = wp_remote_retrieve_body( $response );
221
222
		if ( $entity ) {
223
			$json = json_decode( $entity );
224
		} else {
225
			$json = false;
226
		}
227
228
		if ( 200 != $code || ! empty( $json->error ) ) {
229
			if ( empty( $json->error ) ) {
230
				return new Jetpack_Error( 'unknown', '', $code );
231
			}
232
233
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
234
235
			return new Jetpack_Error( (string) $json->error, $error_description, $code );
236
		}
237
238
		if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
239
			return new Jetpack_Error( 'access_token', '', $code );
240
		}
241
242
		if ( empty( $json->token_type ) || 'X_JETPACK' != strtoupper( $json->token_type ) ) {
243
			return new Jetpack_Error( 'token_type', '', $code );
244
		}
245
246
		if ( empty( $json->scope ) ) {
247
			return new Jetpack_Error( 'scope', 'No Scope', $code );
248
		}
249
250
		@list( $role, $hmac ) = explode( ':', $json->scope );
251
		if ( empty( $role ) || empty( $hmac ) ) {
252
			return new Jetpack_Error( 'scope', 'Malformed Scope', $code );
253
		}
254
255
		if ( Jetpack::sign_role( $role ) !== $json->scope ) {
256
			return new Jetpack_Error( 'scope', 'Invalid Scope', $code );
257
		}
258
259
		if ( ! $cap = Jetpack::translate_role_to_cap( $role ) ) {
260
			return new Jetpack_Error( 'scope', 'No Cap', $code );
261
		}
262
263
		if ( ! current_user_can( $cap ) ) {
264
			return new Jetpack_Error( 'scope', 'current_user_cannot', $code );
265
		}
266
267
		/**
268
		 * Fires after user has successfully received an auth token.
269
		 *
270
		 * @since 3.9.0
271
		 */
272
		do_action( 'jetpack_user_authorized' );
273
274
		return (string) $json->access_token;
275
	}
276
277
	public function get_jetpack() {
278
		return Jetpack::init();
279
	}
280
281
	public function do_exit() {
282
		exit;
283
	}
284
}
285