PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 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 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-connection/src/class-webhooks.php +231 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,231 @@
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 +}