fix Secops issues (#411)

This commit is contained in:
Matthias Pfefferle 2023-09-05 21:03:25 +02:00 committed by GitHub
parent 2ad9bf9148
commit 8dcbe0c6fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 79 deletions

View file

@ -74,7 +74,7 @@ class Mention {
public static function replace_with_links( $result ) { public static function replace_with_links( $result ) {
$metadata = get_remote_metadata_by_actor( $result[0] ); $metadata = get_remote_metadata_by_actor( $result[0] );
if ( ! is_wp_error( $metadata ) && ! empty( $metadata['url'] ) ) { if ( ! empty( $metadata ) && ! is_wp_error( $metadata ) && ! empty( $metadata['url'] ) ) {
$username = ltrim( $result[0], '@' ); $username = ltrim( $result[0], '@' );
if ( ! empty( $metadata['name'] ) ) { if ( ! empty( $metadata['name'] ) ) {
$username = $metadata['name']; $username = $metadata['name'];

View file

@ -114,7 +114,7 @@ class Shortcodes {
/** This filter is documented in wp-includes/post-template.php */ /** This filter is documented in wp-includes/post-template.php */
$excerpt = \apply_filters( 'the_content', $excerpt ); $excerpt = \apply_filters( 'the_content', $excerpt );
$excerpt = \str_replace( ']]>', ']]>', $excerpt ); $excerpt = \str_replace( ']]>', ']]>', $excerpt );
} }
} }

View file

@ -234,7 +234,7 @@ class Signature {
* *
* @param string $key_id The URL to the public key. * @param string $key_id The URL to the public key.
* *
* @return string The public key. * @return WP_Error|string The public key.
*/ */
public static function get_remote_key( $key_id ) { // phpcs:ignore public static function get_remote_key( $key_id ) { // phpcs:ignore
$actor = get_remote_metadata_by_actor( strip_fragment_from_url( $key_id ) ); // phpcs:ignore $actor = get_remote_metadata_by_actor( strip_fragment_from_url( $key_id ) ); // phpcs:ignore
@ -244,7 +244,7 @@ class Signature {
if ( isset( $actor['publicKey']['publicKeyPem'] ) ) { if ( isset( $actor['publicKey']['publicKeyPem'] ) ) {
return \rtrim( $actor['publicKey']['publicKeyPem'] ); // phpcs:ignore return \rtrim( $actor['publicKey']['publicKeyPem'] ); // phpcs:ignore
} }
return null; return new WP_Error( 'activitypub_no_remote_key_found', 'No Public-Key found' );
} }
/** /**

View file

@ -160,7 +160,7 @@ class Followers {
* @param int $user_id The ID of the WordPress User * @param int $user_id The ID of the WordPress User
* @param string $actor The Actor URL * @param string $actor The Actor URL
* *
* @return array|WP_Error The Follower (WP_Term array) or an WP_Error * @return array|WP_Error The Follower (WP_Post array) or an WP_Error
*/ */
public static function add_follower( $user_id, $actor ) { public static function add_follower( $user_id, $actor ) {
$meta = get_remote_metadata_by_actor( $actor ); $meta = get_remote_metadata_by_actor( $actor );
@ -169,29 +169,30 @@ class Followers {
return $meta; return $meta;
} }
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ) );
}
$error = null; $error = null;
$follower = new Follower(); $follower = new Follower();
$follower->from_array( $meta );
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) { $id = $follower->upsert();
$follower->set_id( $actor );
$follower->set_url( $actor ); if ( is_wp_error( $id ) ) {
$error = $meta; return $id;
} else {
$follower->from_array( $meta );
} }
$follower->upsert(); $meta = get_post_meta( $id, 'activitypub_user_id' );
$meta = get_post_meta( $follower->get__id(), 'activitypub_user_id' );
if ( $error ) { if ( $error ) {
self::add_error( $follower->get__id(), $error ); self::add_error( $id, $error );
} }
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
if ( is_array( $meta ) && ! in_array( $user_id, $meta ) ) { if ( is_array( $meta ) && ! in_array( $user_id, $meta ) ) {
add_post_meta( $follower->get__id(), 'activitypub_user_id', $user_id ); add_post_meta( $id, 'activitypub_user_id', $user_id );
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' ); wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
} }

View file

@ -54,7 +54,7 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) {
} }
if ( ! $actor ) { if ( ! $actor ) {
return null; return new WP_Error( 'activitypub_no_valid_actor_identifier', \__( 'The "actor" identifier is not valid', 'activitypub' ), $actor );
} }
if ( is_wp_error( $actor ) ) { if ( is_wp_error( $actor ) ) {
@ -73,7 +73,7 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) {
} }
if ( ! \wp_http_validate_url( $actor ) ) { if ( ! \wp_http_validate_url( $actor ) ) {
$metadata = new \WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor ); $metadata = new WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor );
\set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period. \set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
return $metadata; return $metadata;
} }
@ -95,7 +95,7 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) {
\set_transient( $transient_key, $metadata, WEEK_IN_SECONDS ); \set_transient( $transient_key, $metadata, WEEK_IN_SECONDS );
if ( ! $metadata ) { if ( ! $metadata ) {
$metadata = new \WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor ); $metadata = new WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), $actor );
\set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period. \set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
return $metadata; return $metadata;
} }

View file

@ -1,6 +1,7 @@
<?php <?php
namespace Activitypub\Model; namespace Activitypub\Model;
use WP_Error;
use WP_Query; use WP_Query;
use Activitypub\Activity\Actor; use Activitypub\Activity\Actor;
use Activitypub\Collection\Followers; use Activitypub\Collection\Followers;
@ -110,12 +111,40 @@ class Follower extends Actor {
$this->save(); $this->save();
} }
/**
* Validate the current Follower-Object.
*
* @return boolean True if the verification was successful.
*/
public function is_valid() {
// the minimum required attributes
$required_attributes = array(
'id',
'preferredUsername',
'inbox',
'publicKey',
'publicKeyPem',
);
foreach ( $required_attributes as $attribute ) {
if ( ! $this->get( $attribute ) ) {
return false;
}
}
return true;
}
/** /**
* Save the current Follower-Object. * Save the current Follower-Object.
* *
* @return void * @return int|WP_Error The Post-ID or an WP_Error.
*/ */
public function save() { public function save() {
if ( ! $this->is_valid() ) {
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ) );
}
if ( ! $this->get__id() ) { if ( ! $this->get__id() ) {
global $wpdb; global $wpdb;
@ -147,15 +176,17 @@ class Follower extends Actor {
$post_id = wp_insert_post( $args ); $post_id = wp_insert_post( $args );
$this->_id = $post_id; $this->_id = $post_id;
return $post_id;
} }
/** /**
* Upsert the current Follower-Object. * Upsert the current Follower-Object.
* *
* @return void * @return int|WP_Error The Post-ID or an WP_Error.
*/ */
public function upsert() { public function upsert() {
$this->save(); return $this->save();
} }
/** /**

View file

@ -131,7 +131,7 @@ class Inbox {
return $user; return $user;
} }
$data = $request->get_params(); $data = $request->get_json_params();
$type = $request->get_param( 'type' ); $type = $request->get_param( 'type' );
$type = \strtolower( $type ); $type = \strtolower( $type );
@ -149,7 +149,7 @@ class Inbox {
* @return WP_REST_Response * @return WP_REST_Response
*/ */
public static function shared_inbox_post( $request ) { public static function shared_inbox_post( $request ) {
$data = $request->get_params(); $data = $request->get_json_params();
$type = $request->get_param( 'type' ); $type = $request->get_param( 'type' );
$users = self::extract_recipients( $data ); $users = self::extract_recipients( $data );
@ -331,61 +331,6 @@ class Inbox {
return $params; return $params;
} }
/**
* Handles "Reaction" requests
*
* @param array $object The activity-object
* @param int $user_id The id of the local blog-user
*/
public static function handle_reaction( $object, $user_id ) {
$meta = get_remote_metadata_by_actor( $object['actor'] );
$comment_post_id = \url_to_postid( $object['object'] );
// save only replys and reactions
if ( ! $comment_post_id ) {
return false;
}
$commentdata = array(
'comment_post_ID' => $comment_post_id,
'comment_author' => \esc_attr( $meta['name'] ),
'comment_author_email' => '',
'comment_author_url' => \esc_url_raw( $object['actor'] ),
'comment_content' => \esc_url_raw( $object['actor'] ),
'comment_type' => \esc_attr( \strtolower( $object['type'] ) ),
'comment_parent' => 0,
'comment_meta' => array(
'source_url' => \esc_url_raw( $object['id'] ),
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
'protocol' => 'activitypub',
),
);
// disable flood control
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
// do not require email for AP entries
\add_filter( 'pre_option_require_name_email', '__return_false' );
// No nonce possible for this submission route
\add_filter(
'akismet_comment_nonce',
function() {
return 'inactive';
}
);
$state = \wp_new_comment( $commentdata, true );
\remove_filter( 'pre_option_require_name_email', '__return_false' );
// re-add flood control
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
do_action( 'activitypub_handled_reaction', $object, $user_id, $state, $commentdata );
}
/** /**
* Handles "Create" requests * Handles "Create" requests
* *

View file

@ -43,6 +43,11 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
'name' => 'úser2', 'name' => 'úser2',
'preferredUsername' => 'user2', 'preferredUsername' => 'user2',
), ),
'error@example.com' => array(
'url' => 'https://error.example.com',
'name' => 'error',
'preferredUsername' => 'error',
),
); );
public function set_up() { public function set_up() {
@ -97,6 +102,27 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$this->assertContains( $follower2, $db_followers2 ); $this->assertContains( $follower2, $db_followers2 );
} }
public function test_add_follower_error() {
$pre_http_request = new MockAction();
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
$follower = 'error@example.com';
$result = \Activitypub\Collection\Followers::add_follower( 1, $follower );
$this->assertTrue( is_wp_error( $result ) );
$follower2 = 'https://error.example.com';
$result = \Activitypub\Collection\Followers::add_follower( 1, $follower2 );
$this->assertTrue( is_wp_error( $result ) );
$db_followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertEmpty( $db_followers );
}
public function test_get_follower() { public function test_get_follower() {
$followers = array( 'https://example.com/author/jon' ); $followers = array( 'https://example.com/author/jon' );
$followers2 = array( 'https://user2.example.com' ); $followers2 = array( 'https://user2.example.com' );
@ -268,6 +294,30 @@ class Test_Db_Activitypub_Followers extends WP_UnitTestCase {
$this->assertCount( 1, $meta ); $this->assertCount( 1, $meta );
} }
public function test_migration() {
$pre_http_request = new MockAction();
add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
$followers = array(
'https://example.com/author/jon',
'https://example.og/errors',
'https://example.org/author/doe',
'http://sally.example.org',
'https://error.example.com',
'https://example.net/error',
);
$user_id = 1;
add_user_meta( $user_id, 'activitypub_followers', $followers, true );
\Activitypub\Migration::maybe_migrate();
$db_followers = \Activitypub\Collection\Followers::get_followers( 1 );
$this->assertCount( 3, $db_followers );
}
/** /**
* @dataProvider extract_name_from_uri_content_provider * @dataProvider extract_name_from_uri_content_provider
*/ */