PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9-a.7
Jetpack – WP Security, Backup, Speed, & Growth v15.9-a.7
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / 3rd-party / activitypub.php
jetpack / 3rd-party Last commit date
debug-bar 2 months ago 3rd-party.php 1 week ago activitypub.php 1 week ago amp.php 6 months ago atomic.php 6 months ago bbpress.php 6 months ago beaverbuilder.php 6 months ago bitly.php 6 months ago buddypress.php 6 months ago class-domain-mapping.php 6 months ago class-jetpack-bbpress-rest-api.php 2 years ago class.jetpack-amp-support.php 3 months ago creative-mail.php 6 months ago debug-bar.php 6 months ago jetpack-backup.php 6 months ago jetpack-boost.php 6 months ago qtranslate-x.php 6 months ago vaultpress.php 6 months ago web-stories.php 6 months ago woocommerce-services.php 6 months ago woocommerce.php 6 months ago wpcom-reader.php 6 months ago wpml.php 6 months ago
activitypub.php
208 lines
1 <?php
2 /**
3 * Compatibility shim so Jetpack-signed admin requests can reach the
4 * ActivityPub plugin's auth-gated client-to-server endpoints, used by the
5 * Jetpack-connected site's wp.com Reader to read the timeline and publish
6 * notes.
7 *
8 * Scope:
9 * - Three routes, with method affinity (inbox GET, proxy POST, outbox POST).
10 * - Blog-mode AP sites only; user-mode is out of scope.
11 * - Real OAuth flows are never overridden — when a Bearer is present we
12 * defer to the plugin's normal verification.
13 *
14 * @package automattic/jetpack
15 */
16
17 declare( strict_types = 1 );
18
19 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
20 use Automattic\Jetpack\Connection\Rest_Authentication;
21 use Automattic\Jetpack\Status;
22 use Automattic\Jetpack\Status\Host;
23
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit( 0 );
26 }
27
28 // The upstream filter passes a third `$scope` arg; the shim deliberately
29 // drops it (`accepted_args = 2`) because a Jetpack-signed admin grants full
30 // client-to-server access by design.
31 add_filter( 'activitypub_oauth_check_permission', 'jetpack_activitypub_reader_auth_check_permission', 10, 2 );
32
33 /**
34 * Filter callback for `activitypub_oauth_check_permission`.
35 *
36 * Returns `true` to authorise the request without an AP OAuth bearer when
37 * every scope predicate holds. Returns the incoming `$result` (typically
38 * null) otherwise, letting the plugin's normal OAuth check run.
39 *
40 * `$request` is typed `mixed` rather than `\WP_REST_Request` because the
41 * WordPress filter ABI provides no guarantee — `is_target_route()` performs
42 * the shape check before any method is dispatched on the argument.
43 *
44 * @since 15.9
45 *
46 * @param mixed $result Result from a previous filter, or null.
47 * @param mixed $request The REST request being checked, expected to be a `\WP_REST_Request`.
48 * @return mixed `true` when authorised; `$result` otherwise.
49 */
50 function jetpack_activitypub_reader_auth_check_permission( $result, $request ) {
51 if ( null !== $result ) {
52 return $result;
53 }
54
55 // Only run on sites where the wp.com Reader actually needs the bridge:
56 // connected, non-offline Jetpack sites that aren't wpcom Simple. Simple
57 // sites already share the AP OAuth datastore with the plugin and pass the
58 // standard verify_authentication path.
59 if (
60 ( new Host() )->is_wpcom_simple()
61 || ! ( new Connection_Manager() )->is_connected()
62 || ( new Status() )->is_offline_mode()
63 ) {
64 return $result;
65 }
66
67 // A real OAuth client beat us here. Let the plugin handle it normally.
68 if ( jetpack_activitypub_reader_auth_is_oauth_request() ) {
69 return $result;
70 }
71
72 if ( ! jetpack_activitypub_reader_auth_is_target_route( $request ) ) {
73 return $result;
74 }
75
76 if ( ! jetpack_activitypub_reader_auth_is_jetpack_signed() ) {
77 return $result;
78 }
79
80 // Must follow the signing check: Rest_Authentication installs the wpcom
81 // user on user-token signed requests, so the current user is only trustworthy
82 // after that gate has passed.
83 if ( ! current_user_can( 'manage_options' ) ) {
84 return $result;
85 }
86
87 if ( ! jetpack_activitypub_reader_auth_is_blog_mode() ) {
88 return $result;
89 }
90
91 return true;
92 }
93
94 /**
95 * Whether the current request carries a verified AP OAuth bearer.
96 *
97 * Wrapped so the `Server` class absence in non-AP environments is a clean
98 * `false` rather than a fatal.
99 *
100 * @since 15.9
101 *
102 * @return bool
103 */
104 function jetpack_activitypub_reader_auth_is_oauth_request(): bool {
105 if ( ! class_exists( 'Activitypub\OAuth\Server' ) ) {
106 return false;
107 }
108 return \Activitypub\OAuth\Server::is_oauth_request();
109 }
110
111 /**
112 * Whether the current request was Jetpack-signed (blog or user token).
113 *
114 * Both signing flavours are accepted: the wpcom bridge signs outbound calls
115 * with the user's Jetpack token when one is available and falls back to the
116 * blog token otherwise. Either is sufficient evidence the call originated
117 * from a wpcom shadow request the destination already trusts.
118 *
119 * @since 15.9
120 *
121 * @return bool
122 */
123 function jetpack_activitypub_reader_auth_is_jetpack_signed(): bool {
124 if ( ! class_exists( Rest_Authentication::class ) ) {
125 return false;
126 }
127 return Rest_Authentication::is_signed_with_user_token()
128 || Rest_Authentication::is_signed_with_blog_token();
129 }
130
131 /**
132 * Whether the destination AP plugin is configured to expose a blog actor.
133 *
134 * Accepts both `'blog'` (blog-only) and `'actor_blog'` (per-user + blog).
135 * On `'actor_blog'` sites the blog actor behaves identically to pure
136 * blog-mode and is the only actor the wpcom Reader operates on — the
137 * route patterns are pinned to `user_id=0`, so widening the grant to
138 * arbitrary user actors is not possible here.
139 *
140 * Pure user-mode (`'actor'`) is still rejected: the blog actor doesn't
141 * exist on those sites, so authorizing `user_id=0` routes would be
142 * nonsensical.
143 *
144 * Uses a `null` sentinel default so an unset option is treated as
145 * "unknown, deny" rather than implicitly accepted — the AP plugin's own
146 * option default is `ACTIVITYPUB_ACTOR_MODE` (i.e. `'actor'`), so
147 * falling back to a blog-accepting mode here would silently widen the
148 * grant surface on fresh installs.
149 *
150 * @since 15.9
151 *
152 * @return bool
153 */
154 function jetpack_activitypub_reader_auth_is_blog_mode(): bool {
155 $mode = get_option( 'activitypub_actor_mode', null );
156 return 'blog' === $mode || 'actor_blog' === $mode;
157 }
158
159 /**
160 * Whether the request targets one of the three Reader auth-gated routes.
161 *
162 * Each pattern is anchored to the AP namespace and includes a method affinity,
163 * so callers can't widen the shim by sending an unexpected verb at an allowed
164 * path (e.g. POSTing to inbox).
165 *
166 * @since 15.9
167 *
168 * @param \WP_REST_Request $request The REST request.
169 * @return bool
170 */
171 function jetpack_activitypub_reader_auth_is_target_route( $request ): bool {
172 if ( ! is_object( $request )
173 || ! method_exists( $request, 'get_route' )
174 || ! method_exists( $request, 'get_method' )
175 ) {
176 return false;
177 }
178
179 $route = (string) $request->get_route();
180 $method = strtoupper( (string) $request->get_method() );
181
182 // Patterns are pinned to the blog actor (user_id 0) on purpose: the wpcom
183 // Reader only operates on the blog actor, and granting the OAuth bypass
184 // for arbitrary user ids would silently widen the surface if the AP
185 // plugin ever loosened its downstream `verify_owner` check.
186 static $patterns = array(
187 'GET' => array(
188 '#^/activitypub/\d+\.\d+/(?:users|actors)/0/inbox/?$#',
189 ),
190 'POST' => array(
191 '#^/activitypub/\d+\.\d+/proxy/?$#',
192 '#^/activitypub/\d+\.\d+/(?:users|actors)/0/outbox/?$#',
193 ),
194 );
195
196 if ( ! isset( $patterns[ $method ] ) ) {
197 return false;
198 }
199
200 foreach ( $patterns[ $method ] as $pattern ) {
201 if ( preg_match( $pattern, $route ) ) {
202 return true;
203 }
204 }
205
206 return false;
207 }
208