Skip to content

Commit 2166f35

Browse files
authored
Refactor actor WebFinger resolution logic (#2169)
1 parent 344f7a1 commit 2166f35

File tree

7 files changed

+209
-26
lines changed

7 files changed

+209
-26
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Optimized WebFinger lookups by centralizing and caching account resolution for faster, more consistent handling across lists.

includes/collection/class-remote-actors.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
use Activitypub\Activity\Actor;
1111
use Activitypub\Http;
12+
use Activitypub\Sanitize;
13+
use Activitypub\Webfinger;
1214

1315
use function Activitypub\get_remote_metadata_by_actor;
1416
use function Activitypub\is_actor;
@@ -485,4 +487,41 @@ public static function get_public_key( $key_id ) {
485487

486488
return new \WP_Error( 'activitypub_no_remote_key_found', 'No Public-Key found', array( 'status' => 401 ) );
487489
}
490+
491+
/**
492+
* Get the acct of a remote actor.
493+
*
494+
* @uses Webfinger::uri_to_acct to resolve the acct by the actor URI.
495+
* @uses Webfinger::guess to guess a acct if the actors acct is not resolvable.
496+
*
497+
* @param int $id The ID of the remote actor.
498+
*
499+
* @return string The acct of the remote actor or empty string on failure.
500+
*/
501+
public static function get_acct( $id ) {
502+
$acct = \get_post_meta( $id, '_activitypub_acct', true );
503+
504+
if ( $acct ) {
505+
return $acct;
506+
}
507+
508+
$post = \get_post( $id );
509+
510+
if ( ! $post ) {
511+
return '';
512+
}
513+
514+
$acct = Webfinger::uri_to_acct( $post->guid );
515+
516+
if ( \is_wp_error( $acct ) ) {
517+
$actor = self::get_actor( $post );
518+
$acct = Webfinger::guess( $actor );
519+
}
520+
521+
$acct = Sanitize::webfinger( $acct );
522+
523+
\update_post_meta( $id, '_activitypub_acct', $acct );
524+
525+
return $acct;
526+
}
488527
}

includes/wp-admin/table/class-blocked-actors.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public function prepare_items() {
234234
'post_title' => $actor->get_name() ?? $actor->get_preferred_username(),
235235
'username' => $actor->get_preferred_username(),
236236
'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
237-
'webfinger' => $this->get_webfinger( $actor ),
237+
'webfinger' => Remote_Actors::get_acct( $blocked_actor_post->ID ),
238238
'identifier' => $actor->get_id(),
239239
'modified' => $blocked_actor_post->post_modified_gmt,
240240
);

includes/wp-admin/table/class-followers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ public function prepare_items() {
286286
'post_title' => $actor->get_name() ?? $actor->get_preferred_username(),
287287
'username' => $actor->get_preferred_username(),
288288
'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
289-
'webfinger' => $this->get_webfinger( $actor ),
289+
'webfinger' => Remote_Actors::get_acct( $follower->ID ),
290290
'identifier' => $actor->get_id(),
291291
'modified' => $follower->post_modified_gmt,
292292
);

includes/wp-admin/table/class-following.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public function prepare_items() {
254254
'post_title' => $actor->get_name() ?? $actor->get_preferred_username(),
255255
'username' => $actor->get_preferred_username(),
256256
'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
257-
'webfinger' => $this->get_webfinger( $actor ),
257+
'webfinger' => Remote_Actors::get_acct( $following->ID ),
258258
'status' => Following_Collection::check_status( $this->user_id, $following->ID ),
259259
'identifier' => $actor->get_id(),
260260
'modified' => $following->post_modified_gmt,

includes/wp-admin/table/trait-actor-list-table.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77

88
namespace Activitypub\WP_Admin\Table;
99

10-
use Activitypub\Activity\Actor;
11-
use Activitypub\Webfinger;
12-
1310
/**
1411
* Actor Table Trait.
1512
*/
@@ -29,26 +26,6 @@ public function normalize_search_term( $search ) {
2926
return \trim( $search );
3027
}
3128

32-
/**
33-
* Returns the WebFinger of an actor.
34-
*
35-
* Falls back to the preferred username if the WebFinger lookup fails or
36-
* tries to extract the username from the profile URL.
37-
*
38-
* @param Actor $actor The actor object.
39-
*
40-
* @return string The WebFinger of the actor.
41-
*/
42-
public function get_webfinger( $actor ) {
43-
$webfinger = Webfinger::uri_to_acct( $actor->get_id() );
44-
45-
if ( ! \is_wp_error( $webfinger ) ) {
46-
return $webfinger;
47-
}
48-
49-
return Webfinger::guess( $actor );
50-
}
51-
5229
/**
5330
* Get the action URL for a follower.
5431
*

tests/includes/collection/class-test-remote-actors.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,169 @@ public function test_key_format_handling() {
378378
\remove_filter( 'pre_get_remote_metadata_by_actor', array( $this, 'pre_get_remote_metadata_by_actor' ) );
379379
}
380380

381+
/**
382+
* Test the get_acct method.
383+
*
384+
* @covers ::get_acct
385+
*/
386+
public function test_get_acct() {
387+
// Test 1: Return cached acct from post meta.
388+
$actor = array(
389+
'id' => 'https://remote.example.com/actor/cached-user',
390+
'type' => 'Person',
391+
'url' => 'https://remote.example.com/actor/cached-user',
392+
'inbox' => 'https://remote.example.com/actor/cached-user/inbox',
393+
'name' => 'Cached User',
394+
'preferredUsername' => 'cached',
395+
);
396+
$post_id = Remote_Actors::create( $actor );
397+
\update_post_meta( $post_id, '_activitypub_acct', '[email protected]' );
398+
399+
$result = Remote_Actors::get_acct( $post_id );
400+
$this->assertEquals( '[email protected]', $result );
401+
402+
// Clean up.
403+
\wp_delete_post( $post_id, true );
404+
405+
// Test 2: Return empty string for non-existent post.
406+
$result = Remote_Actors::get_acct( 99999 );
407+
$this->assertEquals( '', $result );
408+
409+
// Test 3: Successful uri_to_acct conversion.
410+
$actor2 = array(
411+
'id' => 'https://remote.example.com/actor/webfinger-user',
412+
'type' => 'Person',
413+
'url' => 'https://remote.example.com/actor/webfinger-user',
414+
'inbox' => 'https://remote.example.com/actor/webfinger-user/inbox',
415+
'name' => 'Webfinger User',
416+
'preferredUsername' => 'webfinger',
417+
);
418+
$post_id2 = Remote_Actors::create( $actor2 );
419+
420+
// Mock successful Webfinger::uri_to_acct.
421+
\add_filter(
422+
'pre_http_request',
423+
function ( $preempt, $parsed_args, $url ) {
424+
if ( strpos( $url, '.well-known/webfinger' ) !== false ) {
425+
return array(
426+
'response' => array( 'code' => 200 ),
427+
'body' => wp_json_encode(
428+
array(
429+
'subject' => 'acct:[email protected]',
430+
'links' => array(
431+
array(
432+
'rel' => 'self',
433+
'type' => 'application/activity+json',
434+
'href' => 'https://remote.example.com/actor/webfinger-user',
435+
),
436+
),
437+
)
438+
),
439+
);
440+
}
441+
return $preempt;
442+
},
443+
10,
444+
3
445+
);
446+
447+
$result = Remote_Actors::get_acct( $post_id2 );
448+
$this->assertEquals( '[email protected]', $result );
449+
450+
// Verify it was cached.
451+
$cached_acct = \get_post_meta( $post_id2, '_activitypub_acct', true );
452+
$this->assertEquals( '[email protected]', $cached_acct );
453+
454+
\remove_all_filters( 'pre_http_request' );
455+
\wp_delete_post( $post_id2, true );
456+
457+
// Test 4: Fallback to Webfinger::guess when uri_to_acct fails.
458+
$actor3 = array(
459+
'id' => 'https://remote.example.com/actor/guess-user',
460+
'type' => 'Person',
461+
'url' => 'https://remote.example.com/actor/guess-user',
462+
'inbox' => 'https://remote.example.com/actor/guess-user/inbox',
463+
'name' => 'Guess User',
464+
'preferredUsername' => 'guess',
465+
);
466+
$post_id3 = Remote_Actors::create( $actor3 );
467+
468+
// Mock failed Webfinger::uri_to_acct (returns WP_Error).
469+
\add_filter(
470+
'pre_http_request',
471+
function ( $preempt, $parsed_args, $url ) {
472+
if ( strpos( $url, '.well-known/webfinger' ) !== false ) {
473+
return array(
474+
'response' => array( 'code' => 404 ),
475+
'body' => 'Not Found',
476+
);
477+
}
478+
return $preempt;
479+
},
480+
10,
481+
3
482+
);
483+
484+
$result = Remote_Actors::get_acct( $post_id3 );
485+
$this->assertEquals( '[email protected]', $result );
486+
487+
// Verify it was cached (without acct: prefix).
488+
$cached_acct = \get_post_meta( $post_id3, '_activitypub_acct', true );
489+
$this->assertEquals( '[email protected]', $cached_acct );
490+
491+
\remove_all_filters( 'pre_http_request' );
492+
\remove_all_filters( 'pre_get_remote_metadata_by_actor' );
493+
\wp_delete_post( $post_id3, true );
494+
495+
// Test 5: Handle acct: prefix removal.
496+
$actor4 = array(
497+
'id' => 'https://remote.example.com/actor/acct-prefix-user',
498+
'type' => 'Person',
499+
'url' => 'https://remote.example.com/actor/acct-prefix-user',
500+
'inbox' => 'https://remote.example.com/actor/acct-prefix-user/inbox',
501+
'name' => 'Acct Prefix User',
502+
'preferredUsername' => 'acctprefix',
503+
);
504+
$post_id4 = Remote_Actors::create( $actor4 );
505+
506+
// Mock Webfinger::uri_to_acct returning acct: prefixed result.
507+
\add_filter(
508+
'pre_http_request',
509+
function ( $preempt, $parsed_args, $url ) {
510+
if ( strpos( $url, '.well-known/webfinger' ) !== false ) {
511+
return array(
512+
'response' => array( 'code' => 200 ),
513+
'body' => wp_json_encode(
514+
array(
515+
'subject' => 'acct:[email protected]',
516+
'links' => array(
517+
array(
518+
'rel' => 'self',
519+
'type' => 'application/activity+json',
520+
'href' => 'https://remote.example.com/actor/acct-prefix-user',
521+
),
522+
),
523+
)
524+
),
525+
);
526+
}
527+
return $preempt;
528+
},
529+
10,
530+
3
531+
);
532+
533+
$result = Remote_Actors::get_acct( $post_id4 );
534+
$this->assertEquals( '[email protected]', $result );
535+
536+
// Verify cached value has acct: prefix removed.
537+
$cached_acct = \get_post_meta( $post_id4, '_activitypub_acct', true );
538+
$this->assertEquals( '[email protected]', $cached_acct );
539+
540+
\remove_all_filters( 'pre_http_request' );
541+
\wp_delete_post( $post_id4, true );
542+
}
543+
381544
/**
382545
* Pre get remote metadata by actor.
383546
*

0 commit comments

Comments
 (0)