diff --git a/activitypub.php b/activitypub.php index e0f7880..23ff78b 100644 --- a/activitypub.php +++ b/activitypub.php @@ -66,6 +66,11 @@ function init() { require_once \dirname( __FILE__ ) . '/includes/class-health-check.php'; \Activitypub\Health_Check::init(); + + require_once \dirname( __FILE__ ) . '/includes/rest/class-server.php'; + \add_filter( 'wp_rest_server_class', function() { + return '\Activitypub\Rest\Server'; + } ); } add_action( 'plugins_loaded', '\Activitypub\init' ); diff --git a/includes/rest/class-inbox.php b/includes/rest/class-inbox.php index e2772ae..3709d4e 100644 --- a/includes/rest/class-inbox.php +++ b/includes/rest/class-inbox.php @@ -14,7 +14,7 @@ class Inbox { */ public static function init() { \add_action( 'rest_api_init', array( '\Activitypub\Rest\Inbox', 'register_routes' ) ); - //\add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 ); + \add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 ); \add_action( 'activitypub_inbox_follow', array( '\Activitypub\Rest\Inbox', 'handle_follow' ), 10, 2 ); \add_action( 'activitypub_inbox_unfollow', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 ); //\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 ); @@ -36,7 +36,7 @@ class Inbox { ); \register_rest_route( - 'activitypub/1.0', '/users/(?P\d+)/inbox', array( + 'activitypub/1.0', '/users/(?P\d+)/inbox', array( array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ), @@ -61,10 +61,6 @@ class Inbox { return $served; } - if ( 'POST' !== $request->get_method() ) { - return $served; - } - $signature = $request->get_header( 'signature' ); if ( ! $signature ) { @@ -73,6 +69,7 @@ class Inbox { $headers = $request->get_headers(); + // verify signature //\Activitypub\Signature::verify_signature( $headers, $key ); return $served; @@ -86,21 +83,13 @@ class Inbox { * @return WP_REST_Response */ public static function user_inbox( $request ) { - $author_id = $request->get_param( 'id' ); + $user_id = $request->get_param( 'user_id' ); - $data = \json_decode( $request->get_body(), true ); + $data = $request->get_params(); + $type = $request->get_param( 'type' ); - if ( ! \is_array( $data ) || ! \array_key_exists( 'type', $data ) ) { - return new \WP_Error( 'rest_invalid_data', \__( 'Invalid payload', 'activitypub' ), array( 'status' => 422 ) ); - } - - $type = 'create'; - if ( ! empty( $data['type'] ) ) { - $type = \strtolower( $data['type'] ); - } - - \do_action( 'activitypub_inbox', $data, $author_id, $type ); - \do_action( "activitypub_inbox_{$type}", $data, $author_id ); + \do_action( 'activitypub_inbox', $data, $user_id, $type ); + \do_action( "activitypub_inbox_{$type}", $data, $user_id ); return new \WP_REST_Response( array(), 202 ); } @@ -113,61 +102,7 @@ class Inbox { * @return WP_Error not yet implemented */ public static function shared_inbox( $request ) { - $data = \json_decode( $request->get_body(), true ); - if ( empty( $data['to'] ) ) { - return new \WP_Error( 'rest_invalid_data', \__( 'No receiving actor set', 'activitypub' ), array( 'status' => 422 ) ); - } - - if ( \filter_var( $data['to'], \FILTER_VALIDATE_URL ) ) { - $author_id = \Activitypub\url_to_authorid( $data['to'] ); - - if ( ! $author_id ) { - return new \WP_Error( 'rest_invalid_data', \__( 'No matching user', 'activitypub' ), array( 'status' => 422 ) ); - } - } else { - // get the identifier at the left of the '@' - $parts = \explode( '@', $data['to'] ); - - if ( 3 === \count( $parts ) ) { - $username = $parts[1]; - $host = $parts[2]; - } elseif ( 2 === \count( $parts ) ) { - $username = $parts[0]; - $host = $parts[1]; - } - - if ( ! $username || ! $host ) { - return new \WP_Error( 'rest_invalid_data', \__( 'Invalid actor identifier', 'activitypub' ), array( 'status' => 422 ) ); - } - - // check domain - if ( ! \wp_parse_url( \home_url(), \PHP_URL_HOST ) !== $host ) { - return new \WP_Error( 'rest_invalid_data', \__( 'Invalid host', 'activitypub' ), array( 'status' => 422 ) ); - } - - $author = \get_user_by( 'login', $username ); - - if ( ! $author ) { - return new \WP_Error( 'rest_invalid_data', \__( 'No matching user', 'activitypub' ), array( 'status' => 422 ) ); - } - - $author_id = $author->ID; - } - - if ( ! \is_array( $data ) || ! \array_key_exists( 'type', $data ) ) { - return new \WP_Error( 'rest_invalid_data', \__( 'Invalid payload', 'activitypub' ), array( 'status' => 422 ) ); - } - - $type = 'create'; - if ( ! empty( $data['type'] ) ) { - $type = \strtolower( $data['type'] ); - } - - \do_action( 'activitypub_inbox', $data, $author_id, $type ); - \do_action( "activitypub_inbox_{$type}", $data, $author_id ); - - return new \WP_REST_Response( array(), 202 ); } /** @@ -182,11 +117,54 @@ class Inbox { 'type' => 'integer', ); - $params['id'] = array( + $params['user_id'] = array( 'required' => true, 'type' => 'integer', ); + $params['id'] = array( + 'required' => true, + 'type' => 'string', + 'validate_callback' => function( $param, $request, $key ) { + if ( ! is_string( $param ) ) { + $param = $param['id']; + } + return ! \Activitypub\is_blacklisted( $param ); + }, + 'sanitize_callback' => 'esc_url_raw', + ); + + $params['actor'] = array( + 'required' => true, + 'type' => array( 'object', 'string' ), + 'validate_callback' => function( $param, $request, $key ) { + if ( ! is_string( $param ) ) { + $param = $param['id']; + } + return ! \Activitypub\is_blacklisted( $param ); + }, + 'sanitize_callback' => function( $param, $request, $key ) { + if ( ! is_string( $param ) ) { + $param = $param['id']; + } + return \esc_url_raw( $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', + ); + return $params; } @@ -197,10 +175,6 @@ class Inbox { * @param int $user_id The id of the local blog-user */ public static function handle_follow( $object, $user_id ) { - if ( ! \array_key_exists( 'actor', $object ) ) { - return new \WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ) ); - } - // save follower \Activitypub\Peer\Followers::add_follower( $object['actor'], $user_id ); @@ -226,10 +200,6 @@ class Inbox { * @param int $user_id The id of the local blog-user */ public static function handle_unfollow( $object, $user_id ) { - if ( ! \array_key_exists( 'actor', $object ) ) { - return new \WP_Error( 'activitypub_no_actor', \__( 'No "Actor" found', 'activitypub' ) ); - } - \Activitypub\Peer\Followers::remove_follower( $object['actor'], $user_id ); } @@ -240,10 +210,6 @@ class Inbox { * @param int $user_id The id of the local blog-user */ public static function handle_reaction( $object, $user_id ) { - if ( ! \array_key_exists( 'actor', $object ) ) { - return new \WP_Error( 'activitypub_no_actor', \__( 'No "Actor" found', 'activitypub' ) ); - } - $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); $commentdata = array( @@ -277,10 +243,6 @@ class Inbox { * @param int $user_id The id of the local blog-user */ public static function handle_create( $object, $user_id ) { - if ( ! \array_key_exists( 'actor', $object ) ) { - return new \WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ) ); - } - $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); $commentdata = array( diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php new file mode 100644 index 0000000..79926fc --- /dev/null +++ b/includes/rest/class-server.php @@ -0,0 +1,31 @@ +get_content_type(); + + // check for content-sub-types like 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' + if ( preg_match( '/application\/([a-zA-Z+_-]+\+)json/', $content_type['value'] ) ) { + $request->set_header( 'Content-Type', 'application/json' ); + } + + // make request filterable + $request = apply_filters( 'activitypub_pre_dispatch_request', $request ); + + return parent::dispatch( $request ); + } +}