| @@ -177,11 +177,21 @@ | ||
| 177 | 177 | ), |
| 178 | 178 | ), |
| 179 | 179 | ); |
| 180 | 180 | |
| 181 | - // When user_id=0 (blog actor), get_current_user_id() also returns 0 for unauthenticated | |
| 182 | - // visitors, so the equality check alone is insufficient — we must test login state first. | |
| 183 | - if ( ! \is_user_logged_in() || ( \get_current_user_id() !== (int) $user_id && ! \current_user_can( 'activitypub' ) ) ) { | |
| 181 | + /* | |
| 182 | + * Whether the current user owns the outbox being queried. Owners see private and | |
| 183 | + * non-public activity types; unauthenticated, federation, and non-owner requests are | |
| 184 | + * limited to the public subset by the visibility filter below. | |
| 185 | + * | |
| 186 | + * Reuse the canonical ownership gate (the same check the OAuth C2S path applies via | |
| 187 | + * maybe_verify_owner()) instead of re-deriving it here: it requires an authenticated | |
| 188 | + * session, matches the requested user by identity, and handles the blog actor via | |
| 189 | + * user_can_act_as_blog(). A global capability never stands in for ownership. | |
| 190 | + */ | |
| 191 | + $is_outbox_owner = true === $this->verify_owner( $request ); | |
| 192 | + | |
| 193 | + if ( ! $is_outbox_owner ) { | |
| 184 | 194 | $args['meta_query'][] = array( |
| 185 | 195 | 'key' => '_activitypub_activity_type', |
| 186 | 196 | 'value' => $activity_types, |
| 187 | 197 | 'compare' => 'IN', |
| @@ -214,9 +224,9 @@ | ||
| 214 | 224 | $query_result = $outbox_query->query( $args ); |
| 215 | 225 | |
| 216 | 226 | $response = array( |
| 217 | 227 | '@context' => Base_Object::JSON_LD_CONTEXT, |
| 218 | - 'id' => get_rest_url_by_path( sprintf( 'actors/%d/outbox', $user_id ) ), | |
| 228 | + 'id' => get_rest_url_by_path( \sprintf( 'actors/%d/outbox', $user_id ) ), | |
| 219 | 229 | 'generator' => 'https://wordpress.org/?v=' . get_masked_wp_version(), |
| 220 | 230 | 'actor' => $user->get_id(), |
| 221 | 231 | 'type' => 'OrderedCollection', |
| 222 | 232 | 'totalItems' => (int) $outbox_query->found_posts, |
| @@ -418,9 +428,9 @@ | ||
| 418 | 428 | } |
| 419 | 429 | |
| 420 | 430 | // Determine if this is an Activity or a bare Object. |
| 421 | 431 | $type = $data['type'] ?? ''; |
| 422 | - $is_activity = in_array( $type, Activity::TYPES, true ); | |
| 432 | + $is_activity = \in_array( $type, Activity::TYPES, true ); | |
| 423 | 433 | |
| 424 | 434 | // If it's a bare object, wrap it in a Create activity. |
| 425 | 435 | if ( ! $is_activity ) { |
| 426 | 436 | $data = $this->wrap_in_create( $data, $user ); |
| @@ -529,9 +539,9 @@ | ||
| 529 | 539 | $addressing[ $field ] = $object_data[ $field ]; |
| 530 | 540 | } |
| 531 | 541 | } |
| 532 | 542 | |
| 533 | - return array_merge( | |
| 543 | + return \array_merge( | |
| 534 | 544 | array( |
| 535 | 545 | '@context' => Base_Object::JSON_LD_CONTEXT, |
| 536 | 546 | 'type' => 'Create', |
| 537 | 547 | 'actor' => $user->get_id(), |
| @@ -574,9 +584,9 @@ | ||
| 574 | 584 | } |
| 575 | 585 | |
| 576 | 586 | // Check object.attributedTo if present. |
| 577 | 587 | $object = $data['object'] ?? $data; |
| 578 | - if ( is_array( $object ) && ! empty( $object['attributedTo'] ) ) { | |
| 588 | + if ( \is_array( $object ) && ! empty( $object['attributedTo'] ) ) { | |
| 579 | 589 | $attributed_to = object_to_uri( $object['attributedTo'] ); |
| 580 | 590 | if ( $attributed_to && $attributed_to !== $user_actor_id ) { |
| 581 | 591 | return new \WP_Error( |
| 582 | 592 | 'activitypub_attribution_mismatch', |
| @@ -625,14 +635,14 @@ | ||
| 625 | 635 | $to = (array) ( $activity['to'] ?? array() ); |
| 626 | 636 | $cc = (array) ( $activity['cc'] ?? array() ); |
| 627 | 637 | |
| 628 | 638 | // Check if public. |
| 629 | - if ( in_array( $public, $to, true ) ) { | |
| 639 | + if ( \in_array( $public, $to, true ) ) { | |
| 630 | 640 | return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC; |
| 631 | 641 | } |
| 632 | 642 | |
| 633 | 643 | // Check if unlisted (public in cc). |
| 634 | - if ( in_array( $public, $cc, true ) ) { | |
| 644 | + if ( \in_array( $public, $cc, true ) ) { | |
| 635 | 645 | return ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC; |
| 636 | 646 | } |
| 637 | 647 | |
| 638 | 648 | // Private (no public addressing). |
| @@ -651,9 +661,9 @@ | ||
| 651 | 661 | * @return array The activity data with required fields ensured. |
| 652 | 662 | */ |
| 653 | 663 | private function ensure_object_id( $data, $user ) { |
| 654 | 664 | // Check if there's an embedded object that needs fields. |
| 655 | - if ( ! isset( $data['object'] ) || ! is_array( $data['object'] ) ) { | |
| 665 | + if ( ! isset( $data['object'] ) || ! \is_array( $data['object'] ) ) { | |
| 656 | 666 | return $data; |
| 657 | 667 | } |
| 658 | 668 | |
| 659 | 669 | $object = &$data['object']; |