| @@ -15,13 +15,16 @@ | ||
| 15 | 15 | |
| 16 | 16 | namespace ThinkRank\API; |
| 17 | 17 | |
| 18 | 18 | use ThinkRank\SEO\Social_Meta_Manager; |
| 19 | +use ThinkRank\API\Traits\Context_Authorization; | |
| 19 | 20 | use WP_REST_Controller; |
| 20 | 21 | use WP_REST_Request; |
| 21 | 22 | use WP_REST_Response; |
| 22 | 23 | use WP_Error; |
| 23 | 24 | |
| 25 | +require_once THINKRANK_PLUGIN_DIR . 'includes/api/traits/trait-context-authorization.php'; | |
| 26 | + | |
| 24 | 27 | // Prevent direct access |
| 25 | 28 | if (!defined('ABSPATH')) { |
| 26 | 29 | exit; |
| 27 | 30 | } |
| @@ -36,8 +39,10 @@ | ||
| 36 | 39 | * @since 1.0.0 |
| 37 | 40 | */ |
| 38 | 41 | class Social_Media_Endpoint extends WP_REST_Controller { |
| 39 | 42 | |
| 43 | + use Context_Authorization; | |
| 44 | + | |
| 40 | 45 | /** |
| 41 | 46 | * Social Meta Manager instance |
| 42 | 47 | * |
| 43 | 48 | * @since 1.0.0 |
| @@ -83,9 +88,10 @@ | ||
| 83 | 88 | [ |
| 84 | 89 | [ |
| 85 | 90 | 'methods' => 'GET', |
| 86 | 91 | 'callback' => [$this, 'get_settings'], |
| 87 | - 'permission_callback' => [$this, 'check_read_permissions'] | |
| 92 | + 'permission_callback' => [$this, 'check_read_permissions'], | |
| 93 | + 'args' => $this->get_context_route_args() | |
| 88 | 94 | ], |
| 89 | 95 | [ |
| 90 | 96 | 'methods' => 'POST', |
| 91 | 97 | 'callback' => [$this, 'update_settings'], |
| @@ -191,14 +197,20 @@ | ||
| 191 | 197 | * |
| 192 | 198 | * @since 1.0.0 |
| 193 | 199 | * |
| 194 | 200 | * @param WP_REST_Request $request Request object |
| 195 | - * @return WP_REST_Response Response object | |
| 201 | + * @return WP_REST_Response|WP_Error Response object, or the context error | |
| 196 | 202 | */ |
| 197 | - public function get_settings(WP_REST_Request $request): WP_REST_Response { | |
| 203 | + public function get_settings(WP_REST_Request $request) { | |
| 198 | 204 | try { |
| 199 | - $context_type = $request->get_param('context_type') ?? 'site'; | |
| 200 | - $context_id = $request->get_param('context_id'); | |
| 205 | + // SECURITY: the settings this returns carry the object's social | |
| 206 | + // title, description and image. update_settings() already authorises | |
| 207 | + // the object; the read has to as well (#385). | |
| 208 | + $context = $this->resolve_request_context($request); | |
| 209 | + if (is_wp_error($context)) { | |
| 210 | + return $context; | |
| 211 | + } | |
| 212 | + [$context_type, $context_id] = $context; | |
| 201 | 213 | |
| 202 | 214 | // Get settings from Social Meta Manager |
| 203 | 215 | $settings = $this->social_manager->get_settings($context_type, $context_id); |
| 204 | 216 | |
| @@ -324,9 +336,11 @@ | ||
| 324 | 336 | * @return WP_REST_Response|WP_Error Response object |
| 325 | 337 | */ |
| 326 | 338 | public function validate_settings(WP_REST_Request $request) { |
| 327 | 339 | try { |
| 328 | - $settings = $request->get_param('settings') ?? []; | |
| 340 | + // `settings` is registered required, so REST rejects the request | |
| 341 | + // before this runs — the old `?? []` default was unreachable. | |
| 342 | + $settings = $request->get_param('settings'); | |
| 329 | 343 | $context_type = $request->get_param('context_type') ?? 'site'; |
| 330 | 344 | $validation_context = $request->get_param('validation_context') ?? 'all'; |
| 331 | 345 | |
| 332 | 346 | // Validate settings using the enhanced Social Meta Manager with tab-specific context |
| @@ -596,62 +610,8 @@ | ||
| 596 | 610 | * @return bool Permission status |
| 597 | 611 | */ |
| 598 | 612 | public function check_manage_permissions(): bool { |
| 599 | 613 | return \ThinkRank\Core\Capability_Manager::current_user_can('thinkrank_social_media'); |
| 600 | - } | |
| 601 | - | |
| 602 | - /** | |
| 603 | - * Validate context type and ID, and the caller's access to that object. | |
| 604 | - * | |
| 605 | - * @since 1.0.0 | |
| 606 | - * | |
| 607 | - * @param string $context_type Context type | |
| 608 | - * @param int|null $context_id Context ID | |
| 609 | - * @return true|WP_Error True when the caller may use this context, WP_Error | |
| 610 | - * otherwise (400 for a shape error, 403 for authorization). | |
| 611 | - */ | |
| 612 | - private function validate_context(string $context_type, ?int $context_id) { | |
| 613 | - $valid_types = ['site', 'post', 'page', 'product']; | |
| 614 | - | |
| 615 | - $invalid = new WP_Error( | |
| 616 | - 'invalid_context', | |
| 617 | - 'Invalid context type or ID provided', | |
| 618 | - ['status' => 400] | |
| 619 | - ); | |
| 620 | - | |
| 621 | - if (!in_array($context_type, $valid_types, true)) { | |
| 622 | - return $invalid; | |
| 623 | - } | |
| 624 | - | |
| 625 | - if ($context_type !== 'site' && (!$context_id || $context_id <= 0)) { | |
| 626 | - return $invalid; | |
| 627 | - } | |
| 628 | - | |
| 629 | - if ($context_id && !get_post($context_id)) { | |
| 630 | - return $invalid; | |
| 631 | - } | |
| 632 | - | |
| 633 | - // SECURITY: everything above establishes that the context *exists*, not | |
| 634 | - // that this caller may see it. `edit_post` is a meta capability, so | |
| 635 | - // map_meta_cap() resolves authorship, published state and | |
| 636 | - // edit_others_posts for this specific post — the same check the write | |
| 637 | - // paths in this class already make, and the one schema | |
| 638 | - // validate_context() makes on its own context routes. Without it a | |
| 639 | - // delegated Social Media user can walk context_id and read titles, | |
| 640 | - // descriptions, authors and dates for drafts, pending posts and other | |
| 641 | - // authors' content (#366). | |
| 642 | - // | |
| 643 | - // Site context is deliberately left to the route's capability gate, so | |
| 644 | - // a delegated Social Media user can still read site-level social meta. | |
| 645 | - if ($context_type !== 'site' && !current_user_can('edit_post', $context_id)) { | |
| 646 | - return new WP_Error( | |
| 647 | - 'rest_forbidden', | |
| 648 | - 'You are not allowed to access this content.', | |
| 649 | - ['status' => 403] | |
| 650 | - ); | |
| 651 | - } | |
| 652 | - | |
| 653 | - return true; | |
| 654 | 614 | } |
| 655 | 615 | |
| 656 | 616 | /** |
| 657 | 617 | * Get arguments for preview endpoint |