Skip to content

Commit

Permalink
Add webfinger as comment author email if available (#1374)
Browse files Browse the repository at this point in the history
* Add webfinger as comment author email if available

* Add upgrade routine

* Add unit tests

* Changelog

* add doc to reverse-discovery spec

---------

Co-authored-by: Matthias Pfefferle <[email protected]>
  • Loading branch information
obenland and pfefferle authored Feb 27, 2025
1 parent 391d9d9 commit e6d443e
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Bumped minimum required WordPress version to 6.4.
* Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly.
* Use webfinger as author email for comments from the Fediverse.

## [5.3.2] - 2025-02-27

Expand Down
54 changes: 54 additions & 0 deletions includes/class-migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ public static function maybe_migrate() {
if ( \version_compare( $version_from_db, '5.3.0', '<' ) ) {
add_action( 'init', 'flush_rewrite_rules', 20 );
}
if ( \version_compare( $version_from_db, 'unreleased', '<' ) ) {
\wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_comment_author_emails' ) );
}

/*
* Add new update routines above this comment. ^
Expand Down Expand Up @@ -632,6 +635,57 @@ public static function create_comment_outbox_items( $batch_size = 50, $offset =
return null;
}

/**
* Update comment author emails with webfinger addresses for ActivityPub comments.
*
* @param int $batch_size Optional. Number of comments to process per batch. Default 50.
* @param int $offset Optional. Number of comments to skip. Default 0.
* @return array|null Array with batch size and offset if there are more comments to process, null otherwise.
*/
public static function update_comment_author_emails( $batch_size = 50, $offset = 0 ) {
$comments = \get_comments(
array(
'number' => $batch_size,
'offset' => $offset,
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => 'protocol',
'value' => 'activitypub',
),
),
)
);

foreach ( $comments as $comment ) {
$comment_author_url = $comment->comment_author_url;
if ( empty( $comment_author_url ) ) {
continue;
}

$webfinger = Webfinger::uri_to_acct( $comment_author_url );
if ( \is_wp_error( $webfinger ) ) {
continue;
}

\wp_update_comment(
array(
'comment_ID' => $comment->comment_ID,
'comment_author_email' => \str_replace( 'acct:', '', $webfinger ),
)
);
}

if ( count( $comments ) === $batch_size ) {
return array(
'batch_size' => $batch_size,
'offset' => $offset + $batch_size,
);
}

return null;
}

/**
* Set the defaults needed for the plugin to work.
*
Expand Down
2 changes: 2 additions & 0 deletions includes/class-webfinger.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public static function resolve( $uri ) {
/**
* Transform a URI to an acct <identifier>@<host>.
*
* @see https://swicg.github.io/activitypub-webfinger/#reverse-discovery
*
* @param string $uri The URI (acct:, mailto:, http:, https:).
*
* @return string|WP_Error Error or acct URI.
Expand Down
10 changes: 9 additions & 1 deletion includes/collection/class-interactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Activitypub\Collection;

use Activitypub\Webfinger;
use WP_Comment_Query;
use Activitypub\Comment;

Expand Down Expand Up @@ -256,12 +257,19 @@ public static function activity_to_comment( $activity ) {
$comment_content = \addslashes( $activity['object']['content'] );
}

$webfinger = Webfinger::uri_to_acct( $url );
if ( is_wp_error( $webfinger ) ) {
$webfinger = '';
} else {
$webfinger = str_replace( 'acct:', '', $webfinger );
}

$commentdata = array(
'comment_author' => \esc_attr( $comment_author ),
'comment_author_url' => \esc_url_raw( $url ),
'comment_content' => $comment_content,
'comment_type' => 'comment',
'comment_author_email' => '',
'comment_author_email' => $webfinger,
'comment_meta' => array(
'source_id' => \esc_url_raw( object_to_uri( $activity['object'] ) ),
'protocol' => 'activitypub',
Expand Down
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ For reasons of data protection, it is not possible to see the followers of other

* Changed: Bumped minimum required WordPress version to 6.4.
* Changed: Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly.
* Changed: Use webfinger as author email for comments from the Fediverse.

= 5.3.2 =

Expand Down
95 changes: 95 additions & 0 deletions tests/includes/class-test-migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -597,4 +597,99 @@ public function test_create_comment_outbox_items_batching() {
$result = Migration::create_comment_outbox_items( 1, 1000 );
$this->assertNull( $result );
}

/**
* Test update_comment_author_emails updates emails with webfinger addresses.
*
* @covers ::update_comment_author_emails
*/
public function test_update_comment_author_emails() {
$author_url = 'https://example.com/users/test';
$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => self::$fixtures['posts'][0],
'comment_author' => 'Test User',
'comment_author_url' => $author_url,
'comment_author_email' => '',
'comment_type' => 'comment',
'comment_meta' => array( 'protocol' => 'activitypub' ),
)
);

// Mock the HTTP request.
\add_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );

$result = Migration::update_comment_author_emails( 50, 0 );

$this->assertNull( $result );

$updated_comment = \get_comment( $comment_id );
$this->assertEquals( '[email protected]', $updated_comment->comment_author_email );

// Clean up.
\remove_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );
\wp_delete_comment( $comment_id, true );
}

/**
* Test update_comment_author_emails handles batching correctly.
*
* @covers ::update_comment_author_emails
*/
public function test_update_comment_author_emails_batching() {
// Create multiple comments.
$comment_ids = array();
for ( $i = 0; $i < 3; $i++ ) {
$comment_ids[] = self::factory()->comment->create(
array(
'comment_post_ID' => self::$fixtures['posts'][0],
'comment_author' => "Test User $i",
'comment_author_url' => "https://example.com/users/test$i",
'comment_author_email' => '',
'comment_content' => "Test comment $i",
'comment_type' => 'comment',
'comment_meta' => array( 'protocol' => 'activitypub' ),
)
);
}

// Mock the HTTP request.
\add_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );

// Process first batch of 2 comments.
$result = Migration::update_comment_author_emails( 2, 0 );
$this->assertEqualSets(
array(
'batch_size' => 2,
'offset' => 2,
),
$result
);

// Process second batch with remaining comment.
$result = Migration::update_comment_author_emails( 2, 2 );
$this->assertNull( $result );

// Verify all comments were updated.
foreach ( $comment_ids as $comment_id ) {
$comment = \get_comment( $comment_id );
$this->assertEquals( '[email protected]', $comment->comment_author_email );

wp_delete_comment( $comment_id, true );
}

\remove_filter( 'pre_http_request', array( $this, 'mock_webfinger' ) );
}

/**
* Mock webfinger response.
*
* @return array
*/
public function mock_webfinger() {
return array(
'body' => wp_json_encode( array( 'subject' => 'acct:[email protected]' ) ),
'response' => array( 'code' => 200 ),
);
}
}
31 changes: 31 additions & 0 deletions tests/includes/collection/class-test-interactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,35 @@ function () {

remove_all_filters( 'pre_get_remote_metadata_by_actor' );
}

/**
* Test activity_to_comment sets webfinger as comment author email.
*
* @covers ::activity_to_comment
*/
public function test_activity_to_comment_sets_webfinger_email() {
$actor_url = 'https://example.com/users/tester';
$activity = array(
'type' => 'Create',
'actor' => $actor_url,
'object' => array(
'content' => 'Test comment content',
'id' => 'https://example.com/activities/1',
),
);

$filter = function () {
return array(
'body' => wp_json_encode( array( 'subject' => 'acct:[email protected]' ) ),
'response' => array( 'code' => 200 ),
);
};
\add_filter( 'pre_http_request', $filter );

$comment_data = Interactions::activity_to_comment( $activity );

$this->assertEquals( '[email protected]', $comment_data['comment_author_email'] );

\remove_filter( 'pre_http_request', $filter );
}
}

0 comments on commit e6d443e

Please sign in to comment.