Completed
Push — update/open-graph-fallbacks ( 560614...bcfa67 )
by Jeremy
12:56
created

class.jetpack-client-server.php (1 issue)

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