Skip to content

Commit 36610f5

Browse files
authored
Improve Validation (#847)
* initial * re add authorisation request * remove unnecessary checks * validate object * these checks are done by the inbox now * only handle activitypub requests
1 parent 2729f2f commit 36610f5

File tree

6 files changed

+101
-82
lines changed

6 files changed

+101
-82
lines changed

includes/handler/class-announce.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@ public static function init() {
3434
* @return void
3535
*/
3636
public static function handle_announce( $array, $user_id, $activity = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
37-
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
38-
return;
39-
}
40-
41-
if ( ! isset( $array['object'] ) ) {
42-
return;
43-
}
44-
4537
// check if Activity is public or not
4638
if ( ! is_activity_public( $array ) ) {
4739
// @todo maybe send email

includes/handler/class-create.php

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ public static function init() {
2121
10,
2222
3
2323
);
24+
25+
\add_filter(
26+
'activitypub_validate_object',
27+
array( self::class, 'validate_object' ),
28+
10,
29+
3
30+
);
2431
}
2532

2633
/**
@@ -33,17 +40,6 @@ public static function init() {
3340
* @return void
3441
*/
3542
public static function handle_create( $array, $user_id, $object = null ) {
36-
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
37-
return;
38-
}
39-
40-
if (
41-
! isset( $array['object'] ) ||
42-
! isset( $array['object']['id'] )
43-
) {
44-
return;
45-
}
46-
4743
// check if Activity is public or not
4844
if ( ! is_activity_public( $array ) ) {
4945
// @todo maybe send email
@@ -67,4 +63,38 @@ public static function handle_create( $array, $user_id, $object = null ) {
6763

6864
\do_action( 'activitypub_handled_create', $array, $user_id, $state, $reaction );
6965
}
66+
67+
/**
68+
* Validate the object
69+
*
70+
* @param bool $valid The validation state
71+
* @param string $param The object parameter
72+
* @param \WP_REST_Request $request The request object
73+
* @param array $array The activity-object
74+
*
75+
* @return bool The validation state: true if valid, false if not
76+
*/
77+
public static function validate_object( $valid, $param, $request ) {
78+
$json_params = $request->get_json_params();
79+
80+
if (
81+
'Create' !== $json_params['type'] ||
82+
is_wp_error( $request )
83+
) {
84+
return $valid;
85+
}
86+
87+
$object = $json_params['object'];
88+
$required = array(
89+
'id',
90+
'inReplyTo',
91+
'content',
92+
);
93+
94+
if ( array_intersect( $required, array_keys( $object ) ) !== $required ) {
95+
return false;
96+
}
97+
98+
return $valid;
99+
}
70100
}

includes/rest/class-inbox.php

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,6 @@ public static function user_inbox_get_parameters() {
191191
public static function user_inbox_post_parameters() {
192192
$params = array();
193193

194-
$params['page'] = array(
195-
'type' => 'integer',
196-
);
197-
198194
$params['user_id'] = array(
199195
'required' => true,
200196
'type' => 'string',
@@ -207,22 +203,21 @@ public static function user_inbox_post_parameters() {
207203

208204
$params['actor'] = array(
209205
'required' => true,
206+
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
210207
'sanitize_callback' => function ( $param, $request, $key ) {
211208
return object_to_uri( $param );
212209
},
213210
);
214211

215212
$params['type'] = array(
216213
'required' => true,
217-
//'type' => 'enum',
218-
//'enum' => array( 'Create' ),
219-
//'sanitize_callback' => function ( $param, $request, $key ) {
220-
// return \strtolower( $param );
221-
//},
222214
);
223215

224216
$params['object'] = array(
225217
'required' => true,
218+
'validate_callback' => function ( $param, $request, $key ) {
219+
return apply_filters( 'activitypub_validate_object', true, $param, $request, $key );
220+
},
226221
);
227222

228223
return $params;
@@ -234,42 +229,11 @@ public static function user_inbox_post_parameters() {
234229
* @return array list of parameters
235230
*/
236231
public static function shared_inbox_post_parameters() {
237-
$params = array();
238-
239-
$params['page'] = array(
240-
'type' => 'integer',
241-
);
242-
243-
$params['id'] = array(
244-
'required' => true,
245-
'type' => 'string',
246-
'sanitize_callback' => 'esc_url_raw',
247-
);
248-
249-
$params['actor'] = array(
250-
'required' => true,
251-
//'type' => array( 'object', 'string' ),
252-
'sanitize_callback' => function ( $param, $request, $key ) {
253-
return object_to_uri( $param );
254-
},
255-
);
256-
257-
$params['type'] = array(
258-
'required' => true,
259-
//'type' => 'enum',
260-
//'enum' => array( 'Create' ),
261-
//'sanitize_callback' => function ( $param, $request, $key ) {
262-
// return \strtolower( $param );
263-
//},
264-
);
265-
266-
$params['object'] = array(
267-
'required' => true,
268-
//'type' => 'object',
269-
);
232+
$params = self::user_inbox_post_parameters();
270233

271234
$params['to'] = array(
272235
'required' => false,
236+
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
273237
'sanitize_callback' => function ( $param, $request, $key ) {
274238
if ( \is_string( $param ) ) {
275239
$param = array( $param );
@@ -280,6 +244,7 @@ public static function shared_inbox_post_parameters() {
280244
);
281245

282246
$params['cc'] = array(
247+
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
283248
'sanitize_callback' => function ( $param, $request, $key ) {
284249
if ( \is_string( $param ) ) {
285250
$param = array( $param );
@@ -290,6 +255,7 @@ public static function shared_inbox_post_parameters() {
290255
);
291256

292257
$params['bcc'] = array(
258+
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
293259
'sanitize_callback' => function ( $param, $request, $key ) {
294260
if ( \is_string( $param ) ) {
295261
$param = array( $param );

includes/rest/class-server.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Server {
2121
public static function init() {
2222
self::register_routes();
2323

24+
\add_filter( 'rest_request_before_callbacks', array( self::class, 'validate_activitypub_requests' ), 9, 3 );
2425
\add_filter( 'rest_request_before_callbacks', array( self::class, 'authorize_activitypub_requests' ), 10, 3 );
2526
}
2627

@@ -77,6 +78,10 @@ public static function authorize_activitypub_requests( $response, $handler, $req
7778
return $response;
7879
}
7980

81+
if ( \is_wp_error( $response ) ) {
82+
return $response;
83+
}
84+
8085
$route = $request->get_route();
8186

8287
// check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
@@ -124,4 +129,51 @@ public static function authorize_activitypub_requests( $response, $handler, $req
124129

125130
return $response;
126131
}
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+
);
175+
}
176+
177+
return $response;
178+
}
127179
}

tests/test-class-activitypub-create-handler.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,10 @@ public function create_test_object( $id = 'https://example.com/123' ) {
5252
);
5353
}
5454

55-
public function test_handle_create_object_unset_rejected() {
56-
$object = $this->create_test_object();
57-
unset( $object['object'] );
58-
$converted = Activitypub\Handler\Create::handle_create( $object, $this->user_id );
59-
$this->assertNull( $converted );
60-
}
61-
6255
public function test_handle_create_non_public_rejected() {
6356
$object = $this->create_test_object();
6457
$object['cc'] = [];
6558
$converted = Activitypub\Handler\Create::handle_create( $object, $this->user_id );
6659
$this->assertNull( $converted );
6760
}
68-
69-
public function test_handle_create_no_id_rejected() {
70-
$object = $this->create_test_object();
71-
unset( $object['object']['id'] );
72-
$converted = Activitypub\Handler\Create::handle_create( $object, $this->user_id );
73-
$this->assertNull( $converted );
74-
}
7561
}

tests/test-class-activitypub-interactions.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,6 @@ public function test_handle_create_rich() {
109109
$this->assertEquals( 'Helloexampleexample', $comment['comment_content'] );
110110
}
111111

112-
public function test_convert_object_to_comment_not_reply_rejected() {
113-
$object = $this->create_test_object();
114-
unset( $object['object']['inReplyTo'] );
115-
$converted = Activitypub\Collection\Interactions::add_comment( $object );
116-
$this->assertFalse( $converted );
117-
}
118-
119112
public function test_convert_object_to_comment_already_exists_rejected() {
120113
$object = $this->create_test_object( 'https://example.com/test_convert_object_to_comment_already_exists_rejected' );
121114
Activitypub\Collection\Interactions::add_comment( $object );

0 commit comments

Comments
 (0)