| @@ -12,8 +12,9 @@ | ||
| 12 | 12 | use Activitypub\Collection\Remote_Posts; |
| 13 | 13 | use Activitypub\Http; |
| 14 | 14 | |
| 15 | 15 | use function Activitypub\is_activity_reply; |
| 16 | +use function Activitypub\object_to_uri; | |
| 16 | 17 | |
| 17 | 18 | /** |
| 18 | 19 | * Handle Update requests. |
| 19 | 20 | */ |
| @@ -28,9 +29,9 @@ | ||
| 28 | 29 | /** |
| 29 | 30 | * Handle "Update" requests. |
| 30 | 31 | * |
| 31 | 32 | * @param array $activity The Activity object. |
| 32 | - * @param int[] $user_ids The user IDs. Always null for Update activities. | |
| 33 | + * @param int[] $user_ids Local recipient user IDs (followers and addressed local actors); may be empty. | |
| 33 | 34 | * @param \Activitypub\Activity\Activity $activity_object The activity object. Default null. |
| 34 | 35 | */ |
| 35 | 36 | public static function handle_update( $activity, $user_ids, $activity_object ) { |
| 36 | 37 | $object_type = $activity['object']['type'] ?? ''; |
| @@ -77,9 +78,9 @@ | ||
| 77 | 78 | /** |
| 78 | 79 | * Update an Object. |
| 79 | 80 | * |
| 80 | 81 | * @param array $activity The Activity object. |
| 81 | - * @param int[]|null $user_ids The user IDs. Always null for Update activities. | |
| 82 | + * @param int[]|null $user_ids Local recipient user IDs (followers and addressed local actors); may be empty. | |
| 82 | 83 | * @param \Activitypub\Activity\Activity $activity_object The activity object. Default null. |
| 83 | 84 | */ |
| 84 | 85 | public static function update_object( $activity, $user_ids, $activity_object ) { |
| 85 | 86 | $result = new \WP_Error( 'activitypub_update_failed', 'Update failed' ); |
| @@ -90,8 +91,12 @@ | ||
| 90 | 91 | $comment_data = Interactions::update_comment( $activity ); |
| 91 | 92 | |
| 92 | 93 | if ( false === $comment_data ) { |
| 93 | 94 | $updated = false; |
| 95 | + } elseif ( \is_wp_error( $comment_data ) ) { | |
| 96 | + // Handled but rejected (e.g. a foreign actor): keep the failure so the | |
| 97 | + // success flag stays false and the Create fallback is not triggered. | |
| 98 | + $result = $comment_data; | |
| 94 | 99 | } elseif ( ! empty( $comment_data['comment_ID'] ) ) { |
| 95 | 100 | $result = \get_comment( $comment_data['comment_ID'] ); |
| 96 | 101 | } |
| 97 | 102 | } elseif ( \get_option( 'activitypub_create_posts', false ) ) { |
| @@ -123,9 +128,9 @@ | ||
| 123 | 128 | /** |
| 124 | 129 | * Update an Actor. |
| 125 | 130 | * |
| 126 | 131 | * @param array $activity The Activity object. |
| 127 | - * @param int[]|null $user_ids The user IDs. Always null for Update activities. | |
| 132 | + * @param int[]|null $user_ids Local recipient user IDs (followers and addressed local actors); may be empty. | |
| 128 | 133 | */ |
| 129 | 134 | public static function update_actor( $activity, $user_ids ) { |
| 130 | 135 | /* |
| 131 | 136 | * Prefer the actor data embedded in the activity object, as it contains |
| @@ -147,12 +152,18 @@ | ||
| 147 | 152 | $actor = $object; |
| 148 | 153 | } |
| 149 | 154 | } |
| 150 | 155 | |
| 151 | - if ( \is_array( $actor ) && isset( $actor['id'] ) ) { | |
| 156 | + /* | |
| 157 | + * An actor may only update itself. Bind the updated object to the activity | |
| 158 | + * actor (the same constraint the Delete handler enforces) so a remote server | |
| 159 | + * cannot overwrite another host's cached actor by sending an Update whose | |
| 160 | + * object.id points at a victim actor. | |
| 161 | + */ | |
| 162 | + if ( \is_array( $actor ) && isset( $actor['id'] ) && object_to_uri( $actor ) === object_to_uri( $activity['actor'] ) ) { | |
| 152 | 163 | $state = Remote_Actors::upsert( $actor ); |
| 153 | 164 | } else { |
| 154 | - $state = new \WP_Error( 'activitypub_update_failed', 'Update failed: missing or invalid actor object in Update activity' ); | |
| 165 | + $state = new \WP_Error( 'activitypub_update_failed', \__( 'Update failed: missing, invalid, or unauthorized actor object in Update activity.', 'activitypub' ) ); | |
| 155 | 166 | $actor = array(); |
| 156 | 167 | } |
| 157 | 168 | |
| 158 | 169 | /** |