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
← All changes | includes/rest/class-server.php +115 -70 2.1.05.3.1 View file →
@@ -1,15 +1,22 @@
1 1 <?php
2 +/**
3 + * Server REST-Class file.
4 + *
5 + * @package Activitypub
6 + */
7 +
2 8 namespace Activitypub\Rest;
3 9
4 -use stdClass;
5 10 use WP_Error;
11 +use WP_REST_Server;
6 12 use WP_REST_Response;
7 13 use Activitypub\Signature;
8 -use Activitypub\Model\Application_User;
9 14
15 +use function Activitypub\use_authorized_fetch;
16 +
10 17 /**
11 - * ActivityPub Server REST-Class
18 + * ActivityPub Server REST-Class.
12 19 *
13 20 * @author Django Doucet
14 21 *
15 22 * @see https://www.w3.org/TR/activitypub/#security-verification
@@ -15,85 +22,51 @@
15 22 * @see https://www.w3.org/TR/activitypub/#security-verification
16 23 */
17 24 class Server {
18 25 /**
19 - * Initialize the class, registering WordPress hooks
26 + * Initialize the class, registering WordPress hooks.
20 27 */
21 28 public static function init() {
22 - self::register_routes();
23 -
24 - \add_filter( 'rest_request_before_callbacks', array( self::class, 'authorize_activitypub_requests' ), 10, 3 );
29 + self::add_hooks();
25 30 }
26 31
27 32 /**
28 - * Register routes
33 + * Add sever hooks.
29 34 */
30 - public static function register_routes() {
31 - \register_rest_route(
32 - ACTIVITYPUB_REST_NAMESPACE,
33 - '/application',
34 - array(
35 - array(
36 - 'methods' => \WP_REST_Server::READABLE,
37 - 'callback' => array( self::class, 'application_actor' ),
38 - 'permission_callback' => '__return_true',
39 - ),
40 - )
41 - );
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 );
42 38 }
43 39
44 40 /**
45 - * Render Application actor profile
41 + * Callback function to authorize an api request.
46 42 *
47 - * @return WP_REST_Response The JSON profile of the Application Actor.
48 - */
49 - public static function application_actor() {
50 - $user = new Application_User();
51 -
52 - $json = $user->to_array();
53 -
54 - $rest_response = new WP_REST_Response( $json, 200 );
55 - $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
56 -
57 - return $rest_response;
58 - }
59 -
60 - /**
61 - * Callback function to authorize each api requests
43 + * The function is meant to be used as part of permission callbacks for rest api endpoints.
62 44 *
63 - * @see WP_REST_Request
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.
64 48 *
65 - * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
66 - * Usually a WP_REST_Response or WP_Error.
67 - * @param array $handler Route handler used for the request.
68 - * @param WP_REST_Request $request Request used to generate the response.
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
69 51 *
70 - * @return mixed|WP_Error The response, error, or modified response.
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.
71 55 */
72 - public static function authorize_activitypub_requests( $response, $handler, $request ) {
56 + public static function verify_signature( $request ) {
73 57 if ( 'HEAD' === $request->get_method() ) {
74 - return $response;
58 + return true;
75 59 }
76 60
77 - $route = $request->get_route();
78 -
79 - // check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
80 - if (
81 - ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE ) ||
82 - \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'webfinger' ) ||
83 - \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'nodeinfo' )
84 - ) {
85 - return $response;
86 - }
87 -
88 61 /**
89 - * Filter to defer signature verification
62 + * Filter to defer signature verification.
90 63 *
91 64 * Skip signature verification for debugging purposes or to reduce load for
92 65 * certain Activity-Types, like "Delete".
93 66 *
94 - * @param bool $defer Whether to defer signature verification.
95 - * @param WP_REST_Request $request The request used to generate the response.
67 + * @param bool $defer Whether to defer signature verification.
68 + * @param \WP_REST_Request $request The request used to generate the response.
96 69 *
97 70 * @return bool Whether to defer signature verification.
98 71 */
99 72 $defer = \apply_filters( 'activitypub_defer_signature_verification', false, $request );
@@ -98,13 +71,17 @@
98 71 */
99 72 $defer = \apply_filters( 'activitypub_defer_signature_verification', false, $request );
100 73
101 74 if ( $defer ) {
102 - return $response;
75 + return true;
103 76 }
104 77
105 - // POST-Requets are always signed
106 - if ( 'GET' !== $request->get_method() ) {
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 + ) {
107 84 $verified_request = Signature::verify_http_signature( $request );
108 85 if ( \is_wp_error( $verified_request ) ) {
109 86 return new WP_Error(
110 87 'activitypub_signature_verification',
@@ -111,18 +88,86 @@
111 88 $verified_request->get_error_message(),
112 89 array( 'status' => 401 )
113 90 );
114 91 }
115 - } elseif ( 'GET' === $request->get_method() && ACTIVITYPUB_AUTHORIZED_FETCH ) { // GET-Requests are only signed in secure mode
116 - $verified_request = Signature::verify_http_signature( $request );
117 - if ( \is_wp_error( $verified_request ) ) {
118 - return new WP_Error(
119 - 'activitypub_signature_verification',
120 - $verified_request->get_error_message(),
121 - array( 'status' => 401 )
122 - );
123 - }
124 92 }
125 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 +
126 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 + );
127 172 }
128 173 }