mirror of
https://github.com/Automattic/wordpress-activitypub
synced 2024-10-18 12:23:32 +00:00
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
This commit is contained in:
parent
2729f2f0e1
commit
36610f5d75
6 changed files with 101 additions and 82 deletions
|
@ -34,14 +34,6 @@ class Announce {
|
|||
* @return void
|
||||
*/
|
||||
public static function handle_announce( $array, $user_id, $activity = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
|
||||
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $array['object'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Activity is public or not
|
||||
if ( ! is_activity_public( $array ) ) {
|
||||
// @todo maybe send email
|
||||
|
|
|
@ -21,6 +21,13 @@ class Create {
|
|||
10,
|
||||
3
|
||||
);
|
||||
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_object' ),
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,17 +40,6 @@ class Create {
|
|||
* @return void
|
||||
*/
|
||||
public static function handle_create( $array, $user_id, $object = null ) {
|
||||
if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
! isset( $array['object'] ) ||
|
||||
! isset( $array['object']['id'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Activity is public or not
|
||||
if ( ! is_activity_public( $array ) ) {
|
||||
// @todo maybe send email
|
||||
|
@ -67,4 +63,38 @@ class Create {
|
|||
|
||||
\do_action( 'activitypub_handled_create', $array, $user_id, $state, $reaction );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the object
|
||||
*
|
||||
* @param bool $valid The validation state
|
||||
* @param string $param The object parameter
|
||||
* @param \WP_REST_Request $request The request object
|
||||
* @param array $array The activity-object
|
||||
*
|
||||
* @return bool The validation state: true if valid, false if not
|
||||
*/
|
||||
public static function validate_object( $valid, $param, $request ) {
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
if (
|
||||
'Create' !== $json_params['type'] ||
|
||||
is_wp_error( $request )
|
||||
) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
$object = $json_params['object'];
|
||||
$required = array(
|
||||
'id',
|
||||
'inReplyTo',
|
||||
'content',
|
||||
);
|
||||
|
||||
if ( array_intersect( $required, array_keys( $object ) ) !== $required ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,10 +191,6 @@ class Inbox {
|
|||
public static function user_inbox_post_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['user_id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
|
@ -207,6 +203,7 @@ class Inbox {
|
|||
|
||||
$params['actor'] = array(
|
||||
'required' => true,
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
return object_to_uri( $param );
|
||||
},
|
||||
|
@ -214,15 +211,13 @@ class Inbox {
|
|||
|
||||
$params['type'] = array(
|
||||
'required' => true,
|
||||
//'type' => 'enum',
|
||||
//'enum' => array( 'Create' ),
|
||||
//'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
// return \strtolower( $param );
|
||||
//},
|
||||
);
|
||||
|
||||
$params['object'] = array(
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $param, $request, $key ) {
|
||||
return apply_filters( 'activitypub_validate_object', true, $param, $request, $key );
|
||||
},
|
||||
);
|
||||
|
||||
return $params;
|
||||
|
@ -234,42 +229,11 @@ class Inbox {
|
|||
* @return array list of parameters
|
||||
*/
|
||||
public static function shared_inbox_post_parameters() {
|
||||
$params = array();
|
||||
|
||||
$params['page'] = array(
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$params['id'] = array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'esc_url_raw',
|
||||
);
|
||||
|
||||
$params['actor'] = array(
|
||||
'required' => true,
|
||||
//'type' => array( 'object', 'string' ),
|
||||
'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
return object_to_uri( $param );
|
||||
},
|
||||
);
|
||||
|
||||
$params['type'] = array(
|
||||
'required' => true,
|
||||
//'type' => 'enum',
|
||||
//'enum' => array( 'Create' ),
|
||||
//'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
// return \strtolower( $param );
|
||||
//},
|
||||
);
|
||||
|
||||
$params['object'] = array(
|
||||
'required' => true,
|
||||
//'type' => 'object',
|
||||
);
|
||||
$params = self::user_inbox_post_parameters();
|
||||
|
||||
$params['to'] = array(
|
||||
'required' => false,
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
if ( \is_string( $param ) ) {
|
||||
$param = array( $param );
|
||||
|
@ -280,6 +244,7 @@ class Inbox {
|
|||
);
|
||||
|
||||
$params['cc'] = array(
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
if ( \is_string( $param ) ) {
|
||||
$param = array( $param );
|
||||
|
@ -290,6 +255,7 @@ class Inbox {
|
|||
);
|
||||
|
||||
$params['bcc'] = array(
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
'sanitize_callback' => function ( $param, $request, $key ) {
|
||||
if ( \is_string( $param ) ) {
|
||||
$param = array( $param );
|
||||
|
|
|
@ -21,6 +21,7 @@ class Server {
|
|||
public static function init() {
|
||||
self::register_routes();
|
||||
|
||||
\add_filter( 'rest_request_before_callbacks', array( self::class, 'validate_activitypub_requests' ), 9, 3 );
|
||||
\add_filter( 'rest_request_before_callbacks', array( self::class, 'authorize_activitypub_requests' ), 10, 3 );
|
||||
}
|
||||
|
||||
|
@ -77,6 +78,10 @@ class Server {
|
|||
return $response;
|
||||
}
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$route = $request->get_route();
|
||||
|
||||
// check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
|
||||
|
@ -124,4 +129,51 @@ class Server {
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to validate incoming ActivityPub requests
|
||||
*
|
||||
* @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
|
||||
* Usually a WP_REST_Response or WP_Error.
|
||||
* @param array $handler Route handler used for the request.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*
|
||||
* @return mixed|WP_Error The response, error, or modified response.
|
||||
*/
|
||||
public static function validate_activitypub_requests( $response, $handler, $request ) {
|
||||
if ( 'HEAD' === $request->get_method() ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$route = $request->get_route();
|
||||
|
||||
if (
|
||||
\is_wp_error( $response ) ||
|
||||
! \str_starts_with( $route, '/' . ACTIVITYPUB_REST_NAMESPACE )
|
||||
) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$params = $request->get_json_params();
|
||||
|
||||
// Type is required for ActivityPub requests, so it fail later in the process
|
||||
if ( ! isset( $params['type'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (
|
||||
ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS &&
|
||||
in_array( $params['type'], array( 'Create', 'Like', 'Announce' ), true )
|
||||
) {
|
||||
return new WP_Error(
|
||||
'activitypub_server_does_not_accept_incoming_interactions',
|
||||
\__( 'This server does not accept incoming interactions.', 'activitypub' ),
|
||||
// We have to use a 2XX status code here, because otherwise the response will be
|
||||
// treated as an error and Mastodon might block this WordPress instance.
|
||||
array( 'status' => 202 )
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,24 +52,10 @@ class Test_Activitypub_Create_Handler extends WP_UnitTestCase {
|
|||
);
|
||||
}
|
||||
|
||||
public function test_handle_create_object_unset_rejected() {
|
||||
$object = $this->create_test_object();
|
||||
unset( $object['object'] );
|
||||
$converted = Activitypub\Handler\Create::handle_create( $object, $this->user_id );
|
||||
$this->assertNull( $converted );
|
||||
}
|
||||
|
||||
public function test_handle_create_non_public_rejected() {
|
||||
$object = $this->create_test_object();
|
||||
$object['cc'] = [];
|
||||
$converted = Activitypub\Handler\Create::handle_create( $object, $this->user_id );
|
||||
$this->assertNull( $converted );
|
||||
}
|
||||
|
||||
public function test_handle_create_no_id_rejected() {
|
||||
$object = $this->create_test_object();
|
||||
unset( $object['object']['id'] );
|
||||
$converted = Activitypub\Handler\Create::handle_create( $object, $this->user_id );
|
||||
$this->assertNull( $converted );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,13 +109,6 @@ class Test_Activitypub_Interactions extends WP_UnitTestCase {
|
|||
$this->assertEquals( 'Helloexampleexample', $comment['comment_content'] );
|
||||
}
|
||||
|
||||
public function test_convert_object_to_comment_not_reply_rejected() {
|
||||
$object = $this->create_test_object();
|
||||
unset( $object['object']['inReplyTo'] );
|
||||
$converted = Activitypub\Collection\Interactions::add_comment( $object );
|
||||
$this->assertFalse( $converted );
|
||||
}
|
||||
|
||||
public function test_convert_object_to_comment_already_exists_rejected() {
|
||||
$object = $this->create_test_object( 'https://example.com/test_convert_object_to_comment_already_exists_rejected' );
|
||||
Activitypub\Collection\Interactions::add_comment( $object );
|
||||
|
|
Loading…
Reference in a new issue