PluginProbe
ActivityPub / 5.3.1
ActivityPub v5.3.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 5.3.1, at includes/rest/class-server.php

174 lines 5.0 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
15 use function Activitypub\use_authorized_fetch;
16
17 /**
18 * ActivityPub Server REST-Class.
19 *
20 * @author Django Doucet
21 *
22 * @see https://www.w3.org/TR/activitypub/#security-verification
23 */
24 class Server {
25 /**
26 * Initialize the class, registering WordPress hooks.
27 */
28 public static function init() {
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_requests' ), 9, 3 );
37 \add_filter( 'rest_request_parameter_order', array( self::class, 'request_parameter_order' ), 10, 2 );
38 }
39
40 /**
41 * Callback function to authorize an api request.
42 *
43 * The function is meant to be used as part of permission callbacks for rest api endpoints.
44 *
45 * It verifies the signature of POST, PUT, PATCH, and DELETE requests, as well as GET requests in secure mode.
46 * You can use the filter 'activitypub_defer_signature_verification' to defer the signature verification.
47 * HEAD requests are always bypassed.
48 *
49 * @see https://www.w3.org/wiki/SocialCG/ActivityPub/Primer/Authentication_Authorization#Authorized_fetch
50 * @see https://swicg.github.io/activitypub-http-signature/#authorized-fetch
51 *
52 * @param \WP_REST_Request $request The request object.
53 *
54 * @return bool|\WP_Error True if the request is authorized, WP_Error if not.
55 */
56 public static function verify_signature( $request ) {
57 if ( 'HEAD' === $request->get_method() ) {
58 return true;
59 }
60
61 /**
62 * Filter to defer signature verification.
63 *
64 * Skip signature verification for debugging purposes or to reduce load for
65 * certain Activity-Types, like "Delete".
66 *
67 * @param bool $defer Whether to defer signature verification.
68 * @param \WP_REST_Request $request The request used to generate the response.
69 *
70 * @return bool Whether to defer signature verification.
71 */
72 $defer = \apply_filters( 'activitypub_defer_signature_verification', false, $request );
73
74 if ( $defer ) {
75 return true;
76 }
77
78 if (
79 // POST-Requests always have to be signed.
80 'GET' !== $request->get_method() ||
81 // GET-Requests only require a signature in secure mode.
82 ( 'GET' === $request->get_method() && use_authorized_fetch() )
83 ) {
84 $verified_request = Signature::verify_http_signature( $request );
85 if ( \is_wp_error( $verified_request ) ) {
86 return new WP_Error(
87 'activitypub_signature_verification',
88 $verified_request->get_error_message(),
89 array( 'status' => 401 )
90 );
91 }
92 }
93
94 return true;
95 }
96
97 /**
98 * Callback function to validate incoming ActivityPub requests
99 *
100 * @param WP_REST_Response|\WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
101 * Usually a WP_REST_Response or WP_Error.
102 * @param array $handler Route handler used for the request.
103 * @param \WP_REST_Request $request Request used to generate the response.
104 *
105 * @return mixed|WP_Error The response, error, or modified response.
106 */
107 public static function validate_requests( $response, $handler, $request ) {
108 if ( 'HEAD' === $request->get_method() ) {
109 return $response;
110 }
111
112 $route = $request->get_route();
113
114 if (
115 \is_wp_error( $response ) ||
116 ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE )
117 ) {
118 return $response;
119 }
120
121 $params = $request->get_json_params();
122
123 // Type is required for ActivityPub requests, so it fail later in the process.
124 if ( ! isset( $params['type'] ) ) {
125 return $response;
126 }
127
128 if (
129 ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS &&
130 in_array( $params['type'], array( 'Create', 'Like', 'Announce' ), true )
131 ) {
132 return new WP_Error(
133 'activitypub_server_does_not_accept_incoming_interactions',
134 \__( 'This server does not accept incoming interactions.', 'activitypub' ),
135 // We have to use a 2XX status code here, because otherwise the response will be
136 // treated as an error and Mastodon might block this WordPress instance.
137 array( 'status' => 202 )
138 );
139 }
140
141 return $response;
142 }
143
144 /**
145 * Modify the parameter priority order for a REST API request.
146 *
147 * @param string[] $order Array of types to check, in order of priority.
148 * @param WP_REST_Request $request The request object.
149 *
150 * @return string[] The modified order of types to check.
151 */
152 public static function request_parameter_order( $order, $request ) {
153 $route = $request->get_route();
154
155 // Check if it is an activitypub request and exclude webfinger and nodeinfo endpoints.
156 if ( ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE ) ) {
157 return $order;
158 }
159
160 $method = $request->get_method();
161
162 if ( WP_REST_Server::CREATABLE !== $method ) {
163 return $order;
164 }
165
166 return array(
167 'JSON',
168 'POST',
169 'URL',
170 'defaults',
171 );
172 }
173 }
174