PluginProbe
ActivityPub / 4.1.1
ActivityPub v4.1.1
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / rest / class-server.php

class-server.php in ActivityPub 4.1.1, at includes/rest/class-server.php

223 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server REST-Class file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Rest;
9
10 use WP_Error;
11 use WP_REST_Server;
12 use WP_REST_Response;
13 use Activitypub\Signature;
14 use Activitypub\Model\Application;
15
16 /**
17 * ActivityPub Server REST-Class.
18 *
19 * @author Django Doucet
20 *
21 * @see https://www.w3.org/TR/activitypub/#security-verification
22 */
23 class Server {
24 /**
25 * Initialize the class, registering WordPress hooks.
26 */
27 public static function init() {
28 self::register_routes();
29 self::add_hooks();
30 }
31
32 /**
33 * Add sever hooks.
34 */
35 public static function add_hooks() {
36 \add_filter( 'rest_request_before_callbacks', array( self::class, 'validate_activitypub_requests' ), 9, 3 );
37 \add_filter( 'rest_request_before_callbacks', array( self::class, 'authorize_activitypub_requests' ), 10, 3 );
38 \add_filter( 'rest_request_parameter_order', array( self::class, 'request_parameter_order' ), 10, 2 );
39 }
40
41 /**
42 * Register routes
43 */
44 public static function register_routes() {
45 \register_rest_route(
46 ACTIVITYPUB_REST_NAMESPACE,
47 '/application',
48 array(
49 array(
50 'methods' => \WP_REST_Server::READABLE,
51 'callback' => array( self::class, 'application_actor' ),
52 'permission_callback' => '__return_true',
53 ),
54 )
55 );
56 }
57
58 /**
59 * Render Application actor profile
60 *
61 * @return WP_REST_Response The JSON profile of the Application Actor.
62 */
63 public static function application_actor() {
64 $user = new Application();
65
66 $json = $user->to_array();
67
68 $rest_response = new WP_REST_Response( $json, 200 );
69 $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
70
71 return $rest_response;
72 }
73
74 /**
75 * Callback function to authorize each api requests
76 *
77 * @see WP_REST_Request
78 *
79 * @see https://www.w3.org/wiki/SocialCG/ActivityPub/Primer/Authentication_Authorization#Authorized_fetch
80 * @see https://swicg.github.io/activitypub-http-signature/#authorized-fetch
81 *
82 * @param WP_REST_Response|\WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
83 * Usually a WP_REST_Response or WP_Error.
84 * @param array $handler Route handler used for the request.
85 * @param \WP_REST_Request $request Request used to generate the response.
86 *
87 * @return mixed|WP_Error The response, error, or modified response.
88 */
89 public static function authorize_activitypub_requests( $response, $handler, $request ) {
90 if ( 'HEAD' === $request->get_method() ) {
91 return $response;
92 }
93
94 if ( \is_wp_error( $response ) ) {
95 return $response;
96 }
97
98 $route = $request->get_route();
99
100 // Check if it is an activitypub request and exclude webfinger and nodeinfo endpoints.
101 if (
102 ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE ) ||
103 \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'webfinger' ) ||
104 \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'nodeinfo' ) ||
105 \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'application' )
106 ) {
107 return $response;
108 }
109
110 /**
111 * Filter to defer signature verification.
112 *
113 * Skip signature verification for debugging purposes or to reduce load for
114 * certain Activity-Types, like "Delete".
115 *
116 * @param bool $defer Whether to defer signature verification.
117 * @param \WP_REST_Request $request The request used to generate the response.
118 *
119 * @return bool Whether to defer signature verification.
120 */
121 $defer = \apply_filters( 'activitypub_defer_signature_verification', false, $request );
122
123 if ( $defer ) {
124 return $response;
125 }
126
127 if (
128 // POST-Requests are always signed.
129 'GET' !== $request->get_method() ||
130 // GET-Requests only require a signature in secure mode.
131 ( 'GET' === $request->get_method() && ACTIVITYPUB_AUTHORIZED_FETCH )
132 ) {
133 $verified_request = Signature::verify_http_signature( $request );
134 if ( \is_wp_error( $verified_request ) ) {
135 return new WP_Error(
136 'activitypub_signature_verification',
137 $verified_request->get_error_message(),
138 array( 'status' => 401 )
139 );
140 }
141 }
142
143 return $response;
144 }
145
146 /**
147 * Callback function to validate incoming ActivityPub requests
148 *
149 * @param WP_REST_Response|\WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
150 * Usually a WP_REST_Response or WP_Error.
151 * @param array $handler Route handler used for the request.
152 * @param \WP_REST_Request $request Request used to generate the response.
153 *
154 * @return mixed|WP_Error The response, error, or modified response.
155 */
156 public static function validate_activitypub_requests( $response, $handler, $request ) {
157 if ( 'HEAD' === $request->get_method() ) {
158 return $response;
159 }
160
161 $route = $request->get_route();
162
163 if (
164 \is_wp_error( $response ) ||
165 ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE )
166 ) {
167 return $response;
168 }
169
170 $params = $request->get_json_params();
171
172 // Type is required for ActivityPub requests, so it fail later in the process.
173 if ( ! isset( $params['type'] ) ) {
174 return $response;
175 }
176
177 if (
178 ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS &&
179 in_array( $params['type'], array( 'Create', 'Like', 'Announce' ), true )
180 ) {
181 return new WP_Error(
182 'activitypub_server_does_not_accept_incoming_interactions',
183 \__( 'This server does not accept incoming interactions.', 'activitypub' ),
184 // We have to use a 2XX status code here, because otherwise the response will be
185 // treated as an error and Mastodon might block this WordPress instance.
186 array( 'status' => 202 )
187 );
188 }
189
190 return $response;
191 }
192
193 /**
194 * Modify the parameter priority order for a REST API request.
195 *
196 * @param string[] $order Array of types to check, in order of priority.
197 * @param WP_REST_Request $request The request object.
198 *
199 * @return string[] The modified order of types to check.
200 */
201 public static function request_parameter_order( $order, $request ) {
202 $route = $request->get_route();
203
204 // Check if it is an activitypub request and exclude webfinger and nodeinfo endpoints.
205 if ( ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE ) ) {
206 return $order;
207 }
208
209 $method = $request->get_method();
210
211 if ( WP_REST_Server::CREATABLE !== $method ) {
212 return $order;
213 }
214
215 return array(
216 'JSON',
217 'POST',
218 'URL',
219 'defaults',
220 );
221 }
222 }
223