PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
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 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-connection/src/class-rest-authentication.php +238 -0 16.2-beta → 16.3-beta View file →
@@ -1,0 +1,238 @@
1 +<?php
2 +/**
3 + * The Jetpack Connection Rest Authentication file.
4 + *
5 + * @package automattic/jetpack-connection
6 + */
7 +
8 +namespace Automattic\Jetpack\Connection;
9 +
10 +use WP_Error;
11 +
12 +/**
13 + * The Jetpack Connection Rest Authentication class.
14 + */
15 +class Rest_Authentication {
16 +
17 + /**
18 + * The rest authentication status.
19 + *
20 + * @since 1.17.0
21 + * @var boolean
22 + */
23 + private $rest_authentication_status = null;
24 +
25 + /**
26 + * The rest authentication type.
27 + * Can be either 'user' or 'blog' depending on whether the request
28 + * is signed with a user or a blog token.
29 + *
30 + * @since 1.29.0
31 + * @var string
32 + */
33 + private $rest_authentication_type = null;
34 +
35 + /**
36 + * The Manager object.
37 + *
38 + * @since 1.17.0
39 + * @var Object
40 + */
41 + private $connection_manager = null;
42 +
43 + /**
44 + * Holds the singleton instance of this class
45 + *
46 + * @since 1.17.0
47 + * @var Object
48 + */
49 + private static $instance = false;
50 +
51 + /**
52 + * Flag used to avoid determine_current_user filter to enter an infinite loop
53 + *
54 + * @since 1.26.0
55 + * @var boolean
56 + */
57 + private $doing_determine_current_user_filter = false;
58 +
59 + /**
60 + * The constructor.
61 + */
62 + private function __construct() {
63 + $this->connection_manager = new Manager();
64 + }
65 +
66 + /**
67 + * Controls the single instance of this class.
68 + *
69 + * @static
70 + */
71 + public static function init() {
72 + if ( ! self::$instance ) {
73 + self::$instance = new self();
74 +
75 + add_filter( 'determine_current_user', array( self::$instance, 'wp_rest_authenticate' ) );
76 + add_filter( 'rest_authentication_errors', array( self::$instance, 'wp_rest_authentication_errors' ) );
77 + }
78 +
79 + return self::$instance;
80 + }
81 +
82 + /**
83 + * Authenticates requests from Jetpack server to WP REST API endpoints.
84 + * Uses the existing XMLRPC request signing implementation.
85 + *
86 + * @param int|bool $user User ID if one has been determined, false otherwise.
87 + *
88 + * @return int|null The user id or null if the request was authenticated via blog token, or not authenticated at all.
89 + */
90 + public function wp_rest_authenticate( $user ) {
91 + if ( $this->doing_determine_current_user_filter ) {
92 + return $user;
93 + }
94 +
95 + $this->doing_determine_current_user_filter = true;
96 +
97 + try {
98 + if ( ! empty( $user ) ) {
99 + // Another authentication method is in effect.
100 + return $user;
101 + }
102 +
103 + add_filter(
104 + 'jetpack_constant_default_value',
105 + __NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
106 + 10,
107 + 2
108 + );
109 +
110 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
111 + if ( ! isset( $_GET['_for'] ) || 'jetpack' !== $_GET['_for'] ) {
112 + // Nothing to do for this authentication method.
113 + return null;
114 + }
115 +
116 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
117 + if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
118 + // Nothing to do for this authentication method.
119 + return null;
120 + }
121 +
122 + // These `rest_invalid_request` errors occur before `verify_xml_rpc_signature()`,
123 + // so the request has not been authenticated as WP.com. Do not report them to
124 + // Error_Handler: they are malformed unauthenticated requests, not connection errors.
125 + if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
126 + $this->rest_authentication_status = new WP_Error(
127 + 'rest_invalid_request',
128 + __( 'The request method is missing.', 'jetpack-connection' ),
129 + array( 'status' => 400 )
130 + );
131 + return null;
132 + }
133 +
134 + // Only support specific request parameters that have been tested and
135 + // are known to work with signature verification. A different method
136 + // can be passed to the WP REST API via the '?_method=' parameter if
137 + // needed.
138 + if ( 'GET' !== $_SERVER['REQUEST_METHOD'] && 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
139 + $this->rest_authentication_status = new WP_Error(
140 + 'rest_invalid_request',
141 + __( 'This request method is not supported.', 'jetpack-connection' ),
142 + array( 'status' => 400 )
143 + );
144 + return null;
145 + }
146 + if ( 'POST' !== $_SERVER['REQUEST_METHOD'] && ! empty( file_get_contents( 'php://input' ) ) ) {
147 + $this->rest_authentication_status = new WP_Error(
148 + 'rest_invalid_request',
149 + __( 'This request method does not support body parameters.', 'jetpack-connection' ),
150 + array( 'status' => 400 )
151 + );
152 + return null;
153 + }
154 +
155 + $verified = $this->connection_manager->verify_xml_rpc_signature();
156 +
157 + if (
158 + $verified &&
159 + isset( $verified['type'] ) &&
160 + 'blog' === $verified['type']
161 + ) {
162 + // Site-level authentication successful.
163 + $this->rest_authentication_status = true;
164 + $this->rest_authentication_type = 'blog';
165 + return null;
166 + }
167 +
168 + if (
169 + $verified &&
170 + isset( $verified['type'] ) &&
171 + 'user' === $verified['type'] &&
172 + ! empty( $verified['user_id'] )
173 + ) {
174 + // User-level authentication successful.
175 + $this->rest_authentication_status = true;
176 + $this->rest_authentication_type = 'user';
177 + return $verified['user_id'];
178 + }
179 +
180 + // Something else went wrong. Probably a signature error.
181 + $this->rest_authentication_status = new WP_Error(
182 + 'rest_invalid_signature',
183 + __( 'The request is not signed correctly.', 'jetpack-connection' ),
184 + array( 'status' => 400 )
185 + );
186 + return null;
187 + } finally {
188 + $this->doing_determine_current_user_filter = false;
189 + }
190 + }
191 +
192 + /**
193 + * Report authentication status to the WP REST API.
194 + *
195 + * @param WP_Error|mixed $value Error from another authentication handler, null if we should handle it, or another value if not.
196 + * @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
197 + */
198 + public function wp_rest_authentication_errors( $value ) {
199 + if ( null !== $value ) {
200 + return $value;
201 + }
202 + return $this->rest_authentication_status;
203 + }
204 +
205 + /**
206 + * Resets the saved authentication state in between testing requests.
207 + */
208 + public function reset_saved_auth_state() {
209 + $this->rest_authentication_status = null;
210 + $this->connection_manager->reset_saved_auth_state();
211 + }
212 +
213 + /**
214 + * Whether the request was signed with a blog token.
215 + *
216 + * @since 1.29.0
217 + *
218 + * @return bool True if the request was signed with a valid blog token, false otherwise.
219 + */
220 + public static function is_signed_with_blog_token() {
221 + $instance = self::init();
222 +
223 + return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type;
224 + }
225 +
226 + /**
227 + * Whether the request was signed with a user token.
228 + *
229 + * @since 6.7.0
230 + *
231 + * @return bool True if the request was signed with a valid user token, false otherwise.
232 + */
233 + public static function is_signed_with_user_token() {
234 + $instance = self::init();
235 +
236 + return true === $instance->rest_authentication_status && 'user' === $instance->rest_authentication_type;
237 + }
238 +}