PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-connection / src / class-webhooks.php

class-webhooks.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-connection/src/class-webhooks.php

232 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Connection Webhooks class.
4 *
5 * @package automattic/jetpack-connection
6 */
7
8 namespace Automattic\Jetpack\Connection;
9
10 use Automattic\Jetpack\CookieState;
11 use Automattic\Jetpack\Roles;
12 use Automattic\Jetpack\Status\Host;
13 use Automattic\Jetpack\Tracking;
14 use Jetpack_Options;
15
16 /**
17 * Connection Webhooks class.
18 */
19 class Webhooks {
20
21 /**
22 * The Connection Manager object.
23 *
24 * @var Manager
25 */
26 private $connection;
27
28 /**
29 * Webhooks constructor.
30 *
31 * @param Manager $connection The Connection Manager object.
32 */
33 public function __construct( $connection ) {
34 $this->connection = $connection;
35 }
36
37 /**
38 * Initialize the webhooks.
39 *
40 * @param Manager $connection The Connection Manager object.
41 */
42 public static function init( $connection ) {
43 $webhooks = new static( $connection );
44
45 add_action( 'init', array( $webhooks, 'controller' ) );
46 add_action( 'load-toplevel_page_jetpack', array( $webhooks, 'fallback_jetpack_controller' ) );
47 }
48
49 /**
50 * Jetpack plugin used to trigger this webhooks in Jetpack::admin_page_load()
51 *
52 * The Jetpack toplevel menu is still accessible for stand-alone plugins, and while there's no content for that page, there are still
53 * actions from Calypso and WPCOM that reach that route regardless of the site having the Jetpack plugin or not. That's why we are still handling it here.
54 */
55 public function fallback_jetpack_controller() {
56 $this->controller( true );
57 }
58
59 /**
60 * The "controller" decides which handler we need to run.
61 *
62 * @param bool $force Do not check if it's a webhook request and just run the controller.
63 */
64 public function controller( $force = false ) {
65 if ( ! $force ) {
66 // The nonce is verified in specific handlers.
67 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
68 if ( empty( $_GET['handler'] ) || 'jetpack-connection-webhooks' !== $_GET['handler'] ) {
69 return;
70 }
71 }
72
73 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
74 if ( isset( $_GET['connect_url_redirect'] ) ) {
75 $this->handle_connect_url_redirect();
76 }
77
78 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
79 if ( empty( $_GET['action'] ) ) {
80 return;
81 }
82
83 // The nonce is verified in specific handlers.
84 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
85 switch ( $_GET['action'] ) {
86 case 'authorize':
87 $this->handle_authorize();
88 $this->do_exit();
89 break; // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though do_exit never returns.
90 case 'authorize_redirect':
91 $this->handle_authorize_redirect();
92 $this->do_exit();
93 break; // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though do_exit never returns.
94 // Class Jetpack::admin_page_load() still handles other cases.
95 }
96 }
97
98 /**
99 * Perform the authorization action.
100 */
101 public function handle_authorize() {
102 if ( $this->connection->is_connected() && $this->connection->is_user_connected() ) {
103 $redirect_url = apply_filters( 'jetpack_client_authorize_already_authorized_url', admin_url() );
104
105 if ( ! empty( $_GET['redirect'] ) ) {
106 $explicit = esc_url_raw( wp_unslash( $_GET['redirect'] ) );
107 if ( wp_validate_redirect( $explicit ) ) {
108 $redirect_url = $explicit;
109 }
110 }
111
112 wp_safe_redirect( $redirect_url );
113
114 return;
115 }
116 do_action( 'jetpack_client_authorize_processing' );
117
118 $data = stripslashes_deep( $_GET ); // We need all request data under the context of an authorization request.
119 $data['auth_type'] = 'client';
120 $roles = new Roles();
121 $role = $roles->translate_current_user_to_role();
122 $redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
123
124 check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" );
125
126 $tracking = new Tracking();
127
128 $result = $this->connection->authorize( $data );
129
130 if ( is_wp_error( $result ) ) {
131 do_action( 'jetpack_client_authorize_error', $result );
132
133 $tracking->record_user_event(
134 'jpc_client_authorize_fail',
135 array(
136 'error_code' => $result->get_error_code(),
137 'error_message' => $result->get_error_message(),
138 )
139 );
140 } else {
141 /**
142 * Fires after the Jetpack client is authorized to communicate with WordPress.com.
143 *
144 * @param int Jetpack Blog ID.
145 *
146 * @since 1.7.0
147 * @since-jetpack 4.2.0
148 */
149 do_action( 'jetpack_client_authorized', Jetpack_Options::get_option( 'id' ) );
150
151 $tracking->record_user_event( 'jpc_client_authorize_success' );
152 }
153
154 $fallback_redirect = apply_filters( 'jetpack_client_authorize_fallback_url', admin_url() );
155 $redirect = wp_validate_redirect( $redirect ) ? $redirect : $fallback_redirect;
156
157 wp_safe_redirect( $redirect );
158 }
159
160 /**
161 * The authorhize_redirect webhook handler
162 */
163 public function handle_authorize_redirect() {
164 $authorize_redirect_handler = new Webhooks\Authorize_Redirect( $this->connection );
165 $authorize_redirect_handler->handle();
166 }
167
168 /**
169 * The `exit` is wrapped into a method so we could mock it.
170 *
171 * @return never
172 */
173 protected function do_exit() {
174 exit( 0 );
175 }
176
177 /**
178 * Handle the `connect_url_redirect` action,
179 * which is usually called to repeat an attempt for user to authorize the connection.
180 *
181 * @return void
182 */
183 public function handle_connect_url_redirect() {
184 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
185 $from = ! empty( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : 'iframe';
186
187 $skip_pricing = filter_input( INPUT_GET, 'skip_pricing', FILTER_VALIDATE_BOOLEAN );
188
189 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- no site changes, sanitization happens in get_authorization_url()
190 $redirect = ! empty( $_GET['redirect_after_auth'] ) ? wp_unslash( $_GET['redirect_after_auth'] ) : false;
191
192 add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
193
194 if ( ! $this->connection->is_user_connected() ) {
195 if ( ! $this->connection->is_connected() ) {
196 $this->connection->register();
197 }
198
199 $connect_url = add_query_arg( 'from', $from, $this->connection->get_authorization_url( null, $redirect ) );
200
201 if ( $skip_pricing ) {
202 $connect_url = add_query_arg( 'skip_pricing', '1', $connect_url );
203 }
204
205 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
206 if ( isset( $_GET['notes_iframe'] ) ) {
207 $connect_url .= '&notes_iframe';
208 }
209 wp_safe_redirect( $connect_url );
210 $this->do_exit();
211 } elseif ( ! isset( $_GET['calypso_env'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
212 ( new CookieState() )->state( 'message', 'already_authorized' );
213 wp_safe_redirect( $redirect );
214 $this->do_exit();
215 } else {
216 if ( 'connect-after-checkout' === $from && $redirect ) {
217 wp_safe_redirect( $redirect );
218 $this->do_exit();
219 }
220 $connect_url = add_query_arg(
221 array(
222 'from' => $from,
223 'already_authorized' => true,
224 ),
225 $this->connection->get_authorization_url()
226 );
227 wp_safe_redirect( $connect_url );
228 $this->do_exit();
229 }
230 }
231 }
232