PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 All 50 releases
← All changes | includes/api/class-social-media-endpoint.php +37 -74 1.30.0 → 2.9.0 View file →
@@ -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
@@ -250,26 +262,16 @@
250 262
251 263 // SECURITY: this route writes the same per-object social overrides
252 264 // as save_social_meta(), so it needs the same object-level guard —
253 265 // the section-level thinkrank_social_media capability alone would
254 - // let a delegated user write to any post (IDOR).
266 + // let a delegated user write to any post (IDOR). validate_context()
267 + // carries that guard for every context route in this class.
255 268 $context_id = $context_id === null ? null : (int) $context_id;
256 - if (!$this->validate_context($context_type, $context_id)) {
257 - return new WP_Error(
258 - 'invalid_context',
259 - 'Invalid context type or ID provided',
260 - ['status' => 400]
261 - );
269 + $context_validation = $this->validate_context($context_type, $context_id);
270 + if (is_wp_error($context_validation)) {
271 + return $context_validation;
262 272 }
263 273
264 - if ($context_type !== 'site' && !current_user_can('edit_post', $context_id)) {
265 - return new WP_Error(
266 - 'rest_forbidden',
267 - 'You are not allowed to edit this content.',
268 - ['status' => 403]
269 - );
270 - }
271 -
272 274 // Drop unrecognized keys so arbitrary client-supplied keys aren't
273 275 // persisted (storage bloat / settings drift). The known set is the
274 276 // context's default settings, exposed through a filter for add-ons.
275 277 $known = array_keys($this->social_manager->get_default_settings($context_type));
@@ -332,11 +334,13 @@
332 334 *
333 335 * @param WP_REST_Request $request Request object
334 336 * @return WP_REST_Response|WP_Error Response object
335 337 */
336 - public function validate_settings(WP_REST_Request $request): WP_REST_Response|WP_Error {
338 + public function validate_settings(WP_REST_Request $request) {
337 339 try {
338 - $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');
339 343 $context_type = $request->get_param('context_type') ?? 'site';
340 344 $validation_context = $request->get_param('validation_context') ?? 'all';
341 345
342 346 // Validate settings using the enhanced Social Meta Manager with tab-specific context
@@ -449,15 +453,13 @@
449 453 try {
450 454 $context_type = $request->get_param('context_type');
451 455 $context_id = (int) $request->get_param('context_id');
452 456
453 - // Validate context
454 - if (!$this->validate_context($context_type, $context_id)) {
455 - return new WP_Error(
456 - 'invalid_context',
457 - 'Invalid context type or ID provided',
458 - ['status' => 400]
459 - );
457 + // Validate context and the caller's access to it. Returns true or a
458 + // WP_Error carrying the right status (400 shape, 403 authorization).
459 + $context_validation = $this->validate_context($context_type, $context_id);
460 + if (is_wp_error($context_validation)) {
461 + return $context_validation;
460 462 }
461 463
462 464 // Get social meta data
463 465 $social_meta = $this->social_manager->get_output_data($context_type, $context_id);
@@ -492,28 +494,16 @@
492 494 $context_type = $request->get_param('context_type');
493 495 $context_id = (int) $request->get_param('context_id');
494 496 $social_data = $request->get_param('social_data') ?? [];
495 497
496 - // Validate context
497 - if (!$this->validate_context($context_type, $context_id)) {
498 - return new WP_Error(
499 - 'invalid_context',
500 - 'Invalid context type or ID provided',
501 - ['status' => 400]
502 - );
498 + // Validate context and the caller's access to it. validate_context()
499 + // now carries the object-level edit_post guard for non-site contexts,
500 + // so this write path inherits the same check it used to make inline.
501 + $context_validation = $this->validate_context($context_type, $context_id);
502 + if (is_wp_error($context_validation)) {
503 + return $context_validation;
503 504 }
504 505
505 - // SECURITY: This route writes per-post social overrides, so require
506 - // object-level edit permission — the global thinkrank_social_media
507 - // capability alone would allow writing to any post (IDOR).
508 - if (!current_user_can('edit_post', $context_id)) {
509 - return new WP_Error(
510 - 'rest_forbidden',
511 - 'You are not allowed to edit this content.',
512 - ['status' => 403]
513 - );
514 - }
515 -
516 506 // Save social meta data (manager signature is
517 507 // save_settings(context_type, context_id, settings)).
518 508 $result = $this->social_manager->save_settings($context_type, $context_id, $social_data);
519 509
@@ -620,35 +610,8 @@
620 610 * @return bool Permission status
621 611 */
622 612 public function check_manage_permissions(): bool {
623 613 return \ThinkRank\Core\Capability_Manager::current_user_can('thinkrank_social_media');
624 - }
625 -
626 - /**
627 - * Validate context type and ID
628 - *
629 - * @since 1.0.0
630 - *
631 - * @param string $context_type Context type
632 - * @param int|null $context_id Context ID
633 - * @return bool Validation status
634 - */
635 - private function validate_context(string $context_type, ?int $context_id): bool {
636 - $valid_types = ['site', 'post', 'page', 'product'];
637 -
638 - if (!in_array($context_type, $valid_types, true)) {
639 - return false;
640 - }
641 -
642 - if ($context_type !== 'site' && (!$context_id || $context_id <= 0)) {
643 - return false;
644 - }
645 -
646 - if ($context_id && !get_post($context_id)) {
647 - return false;
648 - }
649 -
650 - return true;
651 614 }
652 615
653 616 /**
654 617 * Get arguments for preview endpoint