PluginProbe
ActivityPub / 3.2.5
ActivityPub v3.2.5
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 +95 -21 1.0.53.2.5 View file →
@@ -1,11 +1,12 @@
1 1 <?php
2 2 namespace Activitypub\Rest;
3 3
4 4 use stdClass;
5 +use WP_Error;
5 6 use WP_REST_Response;
6 7 use Activitypub\Signature;
7 -use Activitypub\Model\Application_User;
8 +use Activitypub\Model\Application;
8 9
9 10 /**
10 11 * ActivityPub Server REST-Class
11 12 *
@@ -19,8 +20,9 @@
19 20 */
20 21 public static function init() {
21 22 self::register_routes();
22 23
24 + \add_filter( 'rest_request_before_callbacks', array( self::class, 'validate_activitypub_requests' ), 9, 3 );
23 25 \add_filter( 'rest_request_before_callbacks', array( self::class, 'authorize_activitypub_requests' ), 10, 3 );
24 26 }
25 27
26 28 /**
@@ -45,21 +47,16 @@
45 47 *
46 48 * @return WP_REST_Response The JSON profile of the Application Actor.
47 49 */
48 50 public static function application_actor() {
49 - $user = new Application_User();
51 + $user = new Application();
50 52
51 - $user->set_context(
52 - \Activitypub\Activity\Activity::CONTEXT
53 - );
54 -
55 53 $json = $user->to_array();
56 54
57 - $response = new WP_REST_Response( $json, 200 );
55 + $rest_response = new WP_REST_Response( $json, 200 );
56 + $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
58 57
59 - $response->header( 'Content-Type', 'application/activity+json' );
60 -
61 - return $response;
58 + return $rest_response;
62 59 }
63 60
64 61 /**
65 62 * Callback function to authorize each api requests
@@ -65,8 +62,11 @@
65 62 * Callback function to authorize each api requests
66 63 *
67 64 * @see WP_REST_Request
68 65 *
66 + * @see https://www.w3.org/wiki/SocialCG/ActivityPub/Primer/Authentication_Authorization#Authorized_fetch
67 + * @see https://swicg.github.io/activitypub-http-signature/#authorized-fetch
68 + *
69 69 * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
70 70 * Usually a WP_REST_Response or WP_Error.
71 71 * @param array $handler Route handler used for the request.
72 72 * @param WP_REST_Request $request Request used to generate the response.
@@ -73,8 +73,16 @@
73 73 *
74 74 * @return mixed|WP_Error The response, error, or modified response.
75 75 */
76 76 public static function authorize_activitypub_requests( $response, $handler, $request ) {
77 + if ( 'HEAD' === $request->get_method() ) {
78 + return $response;
79 + }
80 +
81 + if ( \is_wp_error( $response ) ) {
82 + return $response;
83 + }
84 +
77 85 $route = $request->get_route();
78 86
79 87 // check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
80 88 if (
@@ -79,26 +87,92 @@
79 87 // check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
80 88 if (
81 89 ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE ) ||
82 90 \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'webfinger' ) ||
83 - \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'nodeinfo' )
91 + \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'nodeinfo' ) ||
92 + \str_starts_with( $route, '/' . \trailingslashit( ACTIVITYPUB_REST_NAMESPACE ) . 'application' )
84 93 ) {
85 94 return $response;
86 95 }
87 96
88 - // POST-Requets are always signed
89 - if ( 'post' === \strtolower( $request->get_method() ) ) {
97 + /**
98 + * Filter to defer signature verification
99 + *
100 + * Skip signature verification for debugging purposes or to reduce load for
101 + * certain Activity-Types, like "Delete".
102 + *
103 + * @param bool $defer Whether to defer signature verification.
104 + * @param WP_REST_Request $request The request used to generate the response.
105 + *
106 + * @return bool Whether to defer signature verification.
107 + */
108 + $defer = \apply_filters( 'activitypub_defer_signature_verification', false, $request );
109 +
110 + if ( $defer ) {
111 + return $response;
112 + }
113 +
114 + if (
115 + // POST-Requests are always signed
116 + 'GET' !== $request->get_method() ||
117 + // GET-Requests only require a signature in secure mode
118 + ( 'GET' === $request->get_method() && ACTIVITYPUB_AUTHORIZED_FETCH )
119 + ) {
90 120 $verified_request = Signature::verify_http_signature( $request );
91 121 if ( \is_wp_error( $verified_request ) ) {
92 - return $verified_request;
122 + return new WP_Error(
123 + 'activitypub_signature_verification',
124 + $verified_request->get_error_message(),
125 + array( 'status' => 401 )
126 + );
93 127 }
94 - } elseif ( 'get' === \strtolower( $request->get_method() ) ) { // GET-Requests are only signed in secure mode
95 - if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
96 - $verified_request = Signature::verify_http_signature( $request );
97 - if ( \is_wp_error( $verified_request ) ) {
98 - return $verified_request;
99 - }
100 - }
128 + }
129 +
130 + return $response;
131 + }
132 +
133 + /**
134 + * Callback function to validate incoming ActivityPub requests
135 + *
136 + * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
137 + * Usually a WP_REST_Response or WP_Error.
138 + * @param array $handler Route handler used for the request.
139 + * @param WP_REST_Request $request Request used to generate the response.
140 + *
141 + * @return mixed|WP_Error The response, error, or modified response.
142 + */
143 + public static function validate_activitypub_requests( $response, $handler, $request ) {
144 + if ( 'HEAD' === $request->get_method() ) {
145 + return $response;
146 + }
147 +
148 + $route = $request->get_route();
149 +
150 + if (
151 + \is_wp_error( $response ) ||
152 + ! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE )
153 + ) {
154 + return $response;
155 + }
156 +
157 + $params = $request->get_json_params();
158 +
159 + // Type is required for ActivityPub requests, so it fail later in the process
160 + if ( ! isset( $params['type'] ) ) {
161 + return $response;
162 + }
163 +
164 + if (
165 + ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS &&
166 + in_array( $params['type'], array( 'Create', 'Like', 'Announce' ), true )
167 + ) {
168 + return new WP_Error(
169 + 'activitypub_server_does_not_accept_incoming_interactions',
170 + \__( 'This server does not accept incoming interactions.', 'activitypub' ),
171 + // We have to use a 2XX status code here, because otherwise the response will be
172 + // treated as an error and Mastodon might block this WordPress instance.
173 + array( 'status' => 202 )
174 + );
101 175 }
102 176
103 177 return $response;
104 178 }