Skip to content

Commit d4e60ef

Browse files
authored
Add Reject handling for Application user follow requests (#2101)
1 parent 6984d23 commit d4e60ef

File tree

6 files changed

+298
-14
lines changed

6 files changed

+298
-14
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: fixed
3+
4+
Prevents Application users from being followed.

includes/class-migration.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ public static function maybe_migrate() {
199199
}
200200
}
201201

202+
if ( \version_compare( $version_from_db, 'unreleased', '<' ) ) {
203+
self::remove_pending_application_user_follow_requests();
204+
}
205+
202206
// Ensure all required cron schedules are registered.
203207
Scheduler::register_schedules();
204208

@@ -1055,4 +1059,20 @@ public static function update_actor_json_storage( $batch_size = 100 ) {
10551059
);
10561060
}
10571061
}
1062+
1063+
/**
1064+
* Removes pending follow requests for the application user.
1065+
*/
1066+
public static function remove_pending_application_user_follow_requests() {
1067+
global $wpdb;
1068+
1069+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
1070+
$wpdb->delete(
1071+
$wpdb->postmeta,
1072+
array(
1073+
'meta_key' => '_activitypub_following', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
1074+
'meta_value' => Actors::APPLICATION_USER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
1075+
)
1076+
);
1077+
}
10581078
}

includes/handler/class-follow.php

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public static function init() {
3333
* @param int $user_id The user ID.
3434
*/
3535
public static function handle_follow( $activity, $user_id ) {
36+
if ( Actors::APPLICATION_USER_ID === $user_id ) {
37+
self::queue_reject( $activity, $user_id );
38+
return;
39+
}
40+
3641
// Save follower.
3742
$remote_actor = Followers::add_follower(
3843
$user_id,
@@ -82,13 +87,11 @@ public static function queue_accept( $actor, $activity_object, $user_id, $remote
8287
// Only send minimal data.
8388
$activity_object = array_intersect_key(
8489
$activity_object,
85-
array_flip(
86-
array(
87-
'id',
88-
'type',
89-
'actor',
90-
'object',
91-
)
90+
array(
91+
'id' => 1,
92+
'type' => 1,
93+
'actor' => 1,
94+
'object' => 1,
9295
)
9396
);
9497

@@ -100,4 +103,31 @@ public static function queue_accept( $actor, $activity_object, $user_id, $remote
100103

101104
add_to_outbox( $activity, null, $user_id, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE );
102105
}
106+
107+
/**
108+
* Send Reject response.
109+
*
110+
* @param array $activity The Activity array.
111+
* @param int $user_id The ID of the WordPress User.
112+
*/
113+
public static function queue_reject( $activity, $user_id ) {
114+
// Only send minimal data.
115+
$origin_activity = array_intersect_key(
116+
$activity,
117+
array(
118+
'id' => 1,
119+
'type' => 1,
120+
'actor' => 1,
121+
'object' => 1,
122+
)
123+
);
124+
125+
$activity = new Activity();
126+
$activity->set_type( 'Reject' );
127+
$activity->set_actor( Actors::get_by_id( $user_id )->get_id() );
128+
$activity->set_object( $origin_activity );
129+
$activity->set_to( array( $origin_activity['actor'] ) );
130+
131+
add_to_outbox( $activity, null, $user_id, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE );
132+
}
103133
}

includes/model/class-application.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Activitypub\Activity\Actor;
1111
use Activitypub\Collection\Actors;
1212

13+
use function Activitypub\home_host;
1314
use function Activitypub\get_rest_url_by_path;
1415

1516
/**
@@ -241,11 +242,10 @@ public function get_public_key() {
241242
* @return string The User description.
242243
*/
243244
public function get_summary() {
244-
return \wpautop(
245-
\wp_kses(
246-
\get_bloginfo( 'description' ),
247-
'default'
248-
)
245+
return sprintf(
246+
/* translators: %s: Domain of the site */
247+
__( 'This is the Application Actor for %s.', 'activitypub' ),
248+
home_host()
249249
);
250250
}
251251

tests/includes/class-test-migration.php

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,4 +994,184 @@ public function test_update_actor_json_storage_broken_json() {
994994

995995
\wp_delete_post( $post_id );
996996
}
997+
998+
/**
999+
* Test remove_pending_application_user_follow_requests removes correct meta entries.
1000+
*
1001+
* @covers ::remove_pending_application_user_follow_requests
1002+
*/
1003+
public function test_remove_pending_application_user_follow_requests() {
1004+
global $wpdb;
1005+
1006+
// Create test posts with various meta entries.
1007+
$post1 = self::factory()->post->create();
1008+
$post2 = self::factory()->post->create();
1009+
$post3 = self::factory()->post->create();
1010+
1011+
// Add _activitypub_following meta with APPLICATION_USER_ID value.
1012+
\add_post_meta( $post1, '_activitypub_following', Actors::APPLICATION_USER_ID );
1013+
\add_post_meta( $post2, '_activitypub_following', Actors::APPLICATION_USER_ID );
1014+
1015+
// Add _activitypub_following meta with different values (should not be removed).
1016+
\add_post_meta( $post3, '_activitypub_following', '123' );
1017+
\add_post_meta( $post1, '_activitypub_following', '456' );
1018+
1019+
// Add other meta keys (should not be affected).
1020+
\add_post_meta( $post1, '_activitypub_other_meta', Actors::APPLICATION_USER_ID );
1021+
\add_post_meta( $post2, 'some_other_meta', Actors::APPLICATION_USER_ID );
1022+
1023+
// Verify initial state.
1024+
$initial_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1025+
$wpdb->prepare(
1026+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following' AND meta_value = %s",
1027+
Actors::APPLICATION_USER_ID
1028+
)
1029+
);
1030+
$this->assertEquals( 2, $initial_count, 'Should have 2 _activitypub_following entries with APPLICATION_USER_ID' );
1031+
1032+
$other_following_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1033+
$wpdb->prepare(
1034+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following' AND meta_value != %s",
1035+
Actors::APPLICATION_USER_ID
1036+
)
1037+
);
1038+
$this->assertEquals( 2, $other_following_count, 'Should have 2 _activitypub_following entries with other values' );
1039+
1040+
// Run the migration.
1041+
Migration::remove_pending_application_user_follow_requests();
1042+
1043+
// Verify APPLICATION_USER_ID entries were removed.
1044+
$remaining_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1045+
$wpdb->prepare(
1046+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following' AND meta_value = %s",
1047+
Actors::APPLICATION_USER_ID
1048+
)
1049+
);
1050+
$this->assertEquals( 0, $remaining_count, 'All _activitypub_following entries with APPLICATION_USER_ID should be removed' );
1051+
1052+
// Verify other _activitypub_following entries remain.
1053+
$remaining_other_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1054+
$wpdb->prepare(
1055+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following' AND meta_value != %s",
1056+
Actors::APPLICATION_USER_ID
1057+
)
1058+
);
1059+
$this->assertEquals( 2, $remaining_other_count, 'Other _activitypub_following entries should remain' );
1060+
1061+
// Verify other meta keys are unaffected.
1062+
$this->assertEquals( Actors::APPLICATION_USER_ID, \get_post_meta( $post1, '_activitypub_other_meta', true ), 'Other meta keys should not be affected' );
1063+
$this->assertEquals( Actors::APPLICATION_USER_ID, \get_post_meta( $post2, 'some_other_meta', true ), 'Other meta keys should not be affected' );
1064+
1065+
// Clean up.
1066+
\wp_delete_post( $post1, true );
1067+
\wp_delete_post( $post2, true );
1068+
\wp_delete_post( $post3, true );
1069+
}
1070+
1071+
/**
1072+
* Test remove_pending_application_user_follow_requests with no matching entries.
1073+
*
1074+
* @covers ::remove_pending_application_user_follow_requests
1075+
*/
1076+
public function test_remove_pending_application_user_follow_requests_no_matches() {
1077+
global $wpdb;
1078+
1079+
// Create test posts with non-matching meta entries.
1080+
$post1 = self::factory()->post->create();
1081+
$post2 = self::factory()->post->create();
1082+
1083+
// Add _activitypub_following meta with different values.
1084+
\add_post_meta( $post1, '_activitypub_following', '123' );
1085+
\add_post_meta( $post2, '_activitypub_following', '456' );
1086+
1087+
// Add other meta keys with APPLICATION_USER_ID.
1088+
\add_post_meta( $post1, '_activitypub_other_meta', Actors::APPLICATION_USER_ID );
1089+
\add_post_meta( $post2, 'different_meta', Actors::APPLICATION_USER_ID );
1090+
1091+
// Get initial counts.
1092+
$initial_following_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1093+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following'"
1094+
);
1095+
$initial_total_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1096+
"SELECT COUNT(*) FROM {$wpdb->postmeta}"
1097+
);
1098+
1099+
// Run the migration.
1100+
Migration::remove_pending_application_user_follow_requests();
1101+
1102+
// Verify no entries were removed.
1103+
$final_following_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1104+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following'"
1105+
);
1106+
$final_total_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1107+
"SELECT COUNT(*) FROM {$wpdb->postmeta}"
1108+
);
1109+
1110+
$this->assertEquals( $initial_following_count, $final_following_count, 'No _activitypub_following entries should be removed' );
1111+
$this->assertEquals( $initial_total_count, $final_total_count, 'Total meta count should remain the same' );
1112+
1113+
// Verify specific entries remain.
1114+
$this->assertEquals( '123', \get_post_meta( $post1, '_activitypub_following', true ), '_activitypub_following with different value should remain' );
1115+
$this->assertEquals( '456', \get_post_meta( $post2, '_activitypub_following', true ), '_activitypub_following with different value should remain' );
1116+
$this->assertEquals( Actors::APPLICATION_USER_ID, \get_post_meta( $post1, '_activitypub_other_meta', true ), 'Other meta keys should not be affected' );
1117+
$this->assertEquals( Actors::APPLICATION_USER_ID, \get_post_meta( $post2, 'different_meta', true ), 'Other meta keys should not be affected' );
1118+
1119+
// Clean up.
1120+
\wp_delete_post( $post1, true );
1121+
\wp_delete_post( $post2, true );
1122+
}
1123+
1124+
/**
1125+
* Test remove_pending_application_user_follow_requests with multiple APPLICATION_USER_ID entries on same post.
1126+
*
1127+
* @covers ::remove_pending_application_user_follow_requests
1128+
*/
1129+
public function test_remove_pending_application_user_follow_requests_multiple_entries() {
1130+
global $wpdb;
1131+
1132+
// Create test post.
1133+
$post_id = self::factory()->post->create();
1134+
1135+
// Add multiple _activitypub_following meta entries with APPLICATION_USER_ID.
1136+
\add_post_meta( $post_id, '_activitypub_following', Actors::APPLICATION_USER_ID );
1137+
\add_post_meta( $post_id, '_activitypub_following', Actors::APPLICATION_USER_ID );
1138+
\add_post_meta( $post_id, '_activitypub_following', Actors::APPLICATION_USER_ID );
1139+
1140+
// Add one with different value.
1141+
\add_post_meta( $post_id, '_activitypub_following', '789' );
1142+
1143+
// Verify initial state.
1144+
$initial_app_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1145+
$wpdb->prepare(
1146+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following' AND meta_value = %s",
1147+
Actors::APPLICATION_USER_ID
1148+
)
1149+
);
1150+
$this->assertEquals( 3, $initial_app_count, 'Should have 3 APPLICATION_USER_ID entries' );
1151+
1152+
// Run the migration.
1153+
Migration::remove_pending_application_user_follow_requests();
1154+
1155+
// Verify all APPLICATION_USER_ID entries were removed.
1156+
$remaining_app_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1157+
$wpdb->prepare(
1158+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_activitypub_following' AND meta_value = %s",
1159+
Actors::APPLICATION_USER_ID
1160+
)
1161+
);
1162+
$this->assertEquals( 0, $remaining_app_count, 'All APPLICATION_USER_ID entries should be removed' );
1163+
1164+
// Verify the other entry remains.
1165+
$remaining_other_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1166+
$wpdb->prepare(
1167+
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_activitypub_following'",
1168+
$post_id
1169+
)
1170+
);
1171+
$this->assertEquals( 1, $remaining_other_count, 'One _activitypub_following entry should remain' );
1172+
$this->assertEquals( '789', \get_post_meta( $post_id, '_activitypub_following', true ), 'Non-APPLICATION_USER_ID entry should remain' );
1173+
1174+
// Clean up.
1175+
\wp_delete_post( $post_id, true );
1176+
}
9971177
}

tests/includes/handler/class-test-follow.php

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,56 @@ public static function wpTearDownAfterClass() {
4545
wp_delete_user( self::$user_id );
4646
}
4747

48+
/**
49+
* Test handle_follow method.
50+
*
51+
* @covers ::handle_follow
52+
*/
53+
public function test_handle_follow() {
54+
$local_actor = Actors::get_by_id( Actors::APPLICATION_USER_ID );
55+
$actor = 'https://example.com/actor';
56+
$activity_object = array(
57+
'id' => 'https://example.com/activity/123',
58+
'type' => 'Follow',
59+
'actor' => $actor,
60+
'object' => $local_actor->get_id(),
61+
);
62+
63+
Follow::handle_follow( $activity_object, Actors::APPLICATION_USER_ID );
64+
65+
$outbox_posts = \get_posts(
66+
array(
67+
'post_type' => Outbox::POST_TYPE,
68+
'post_status' => 'pending',
69+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
70+
'meta_query' => array(
71+
array(
72+
'key' => '_activitypub_activity_type',
73+
'value' => 'Accept',
74+
),
75+
),
76+
)
77+
);
78+
$this->assertEmpty( $outbox_posts );
79+
80+
$outbox_posts = \get_posts(
81+
array(
82+
'post_type' => Outbox::POST_TYPE,
83+
'post_status' => 'pending',
84+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
85+
'meta_query' => array(
86+
array(
87+
'key' => '_activitypub_activity_type',
88+
'value' => 'Reject',
89+
),
90+
),
91+
)
92+
);
93+
$this->assertNotEmpty( $outbox_posts );
94+
95+
_delete_all_posts();
96+
}
97+
4898
/**
4999
* Test queue_accept method.
50100
*
@@ -64,7 +114,7 @@ public function test_queue_accept() {
64114
$wp_error = new \WP_Error( 'test_error', 'Test Error' );
65115
Follow::queue_accept( $actor, $activity_object, self::$user_id, $wp_error );
66116

67-
$outbox_posts = get_posts(
117+
$outbox_posts = \get_posts(
68118
array(
69119
'post_type' => Outbox::POST_TYPE,
70120
'author' => self::$user_id,
@@ -100,7 +150,7 @@ function () use ( $actor ) {
100150

101151
Follow::queue_accept( $actor, $activity_object, self::$user_id, $remote_actor );
102152

103-
$outbox_posts = get_posts(
153+
$outbox_posts = \get_posts(
104154
array(
105155
'post_type' => Outbox::POST_TYPE,
106156
'author' => self::$user_id,

0 commit comments

Comments
 (0)