From 4452be46f8ee3e4a47dee97cfc27050049e4b90b Mon Sep 17 00:00:00 2001 From: mehmoodak <31419912+mehmoodak@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:56:11 +0500 Subject: [PATCH] Improve checks on proxy endpoints --- CHANGELOG.md | 6 ++++ README.md | 2 +- package-lock.json | 4 +-- package.json | 2 +- .../class-analytics-posts-api-proxy.php | 12 +------ src/Endpoints/class-base-api-proxy.php | 32 +++++++++++++++++-- src/Endpoints/class-related-api-proxy.php | 12 +------ .../AnalyticsPostsProxyEndpointTest.php | 19 +++++++++++ tests/Integration/TestCase.php | 10 ++++++ tests/e2e/utils.js | 2 +- wp-parsely.php | 4 +-- 11 files changed, 73 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f900c72..8de6cb024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.5.3](https://github.com/Parsely/wp-parsely/compare/3.5.2...3.5.3) - 2023-02-13 + +### Fixed + +- Improve checks on proxy endpoints + ## [3.5.2](https://github.com/Parsely/wp-parsely/compare/3.5.1...3.5.2) - 2022-09-27 ### Changed diff --git a/README.md b/README.md index d80843647..0cbb9ba9c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Parse.ly -Stable tag: 3.5.2 +Stable tag: 3.5.3 Requires at least: 5.0 Tested up to: 6.0.2 Requires PHP: 7.1 diff --git a/package-lock.json b/package-lock.json index 33dc50d17..437281aec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wp-parsely", - "version": "3.5.2", + "version": "3.5.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wp-parsely", - "version": "3.5.2", + "version": "3.5.3", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/dom-ready": "^3.9.0", diff --git a/package.json b/package.json index 2e5467c65..67468f3ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wp-parsely", - "version": "3.5.2", + "version": "3.5.3", "private": true, "description": "The Parse.ly plugin facilitates real-time and historical analytics to your content through a platform designed and built for digital publishing.", "author": "parsely, hbbtstar, jblz, mikeyarce, GaryJ, parsely_mike, pauarge", diff --git a/src/Endpoints/class-analytics-posts-api-proxy.php b/src/Endpoints/class-analytics-posts-api-proxy.php index b804300c3..7a74597a8 100644 --- a/src/Endpoints/class-analytics-posts-api-proxy.php +++ b/src/Endpoints/class-analytics-posts-api-proxy.php @@ -22,7 +22,7 @@ final class Analytics_Posts_API_Proxy extends Base_API_Proxy { * Registers the endpoint's WP REST route. */ public function run(): void { - $this->register_endpoint( '/analytics/posts' ); + $this->register_endpoint( '/analytics/posts', 'publish_posts' ); } /** @@ -61,14 +61,4 @@ static function( stdClass $item ) use ( $date_format, $stats_base_url ) { return $result; } - - /** - * Determines if there are enough permissions to call the endpoint. - * - * @return bool - */ - public function permission_callback(): bool { - // Unauthenticated. - return true; - } } diff --git a/src/Endpoints/class-base-api-proxy.php b/src/Endpoints/class-base-api-proxy.php index 33d76d21b..cdbf6880f 100644 --- a/src/Endpoints/class-base-api-proxy.php +++ b/src/Endpoints/class-base-api-proxy.php @@ -28,6 +28,15 @@ abstract class Base_API_Proxy { */ protected $parsely; + /** + * Capability of the user based on which we should allow access to endpoint. + * + * `null` should be used for all public endpoints. + * + * @var string|null + */ + protected $user_capability; + /** * Proxy object which does the actual calls to the Parse.ly API. * @@ -60,7 +69,19 @@ abstract public function get_items( WP_REST_Request $request ): stdClass; * * @return bool */ - abstract public function permission_callback(): bool; + public function permission_callback(): bool { + // This endpoint does not require any capability checks. + if ( is_null( $this->user_capability ) ) { + return true; + } + + // The user has the required capability to access this endpoint. + if ( current_user_can( $this->user_capability ) ) { + return true; + } + + return false; + } /** * Constructor. @@ -77,9 +98,13 @@ public function __construct( Parsely $parsely, Proxy $proxy ) { /** * Registers the endpoint's WP REST route. * - * @param string $endpoint The endpoint (e.g. /analytics/posts). + * @param string $endpoint The endpoint's route (e.g. /stats/posts). + * @param string|null $user_capability Capability of the user based on which we should allow access to endpoint. + * @param bool $show_in_index Show endpoint in /wp-json view if TRUE. */ - protected function register_endpoint( string $endpoint ): void { + protected function register_endpoint( string $endpoint, ?string $user_capability, $show_in_index = false ): void { + $this->user_capability = $user_capability; + $filter_key = trim( str_replace( '/', '_', $endpoint ), '_' ); if ( ! apply_filters( 'wp_parsely_enable_' . $filter_key . '_api_proxy', true ) ) { return; @@ -105,6 +130,7 @@ protected function register_endpoint( string $endpoint ): void { 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'permission_callback' ), 'args' => $get_items_args, + 'show_in_index' => $show_in_index, ), ); diff --git a/src/Endpoints/class-related-api-proxy.php b/src/Endpoints/class-related-api-proxy.php index 1f95b17d0..8cb55fd7d 100644 --- a/src/Endpoints/class-related-api-proxy.php +++ b/src/Endpoints/class-related-api-proxy.php @@ -22,7 +22,7 @@ final class Related_API_Proxy extends Base_API_Proxy { * Registers the endpoint's WP REST route. */ public function run(): void { - $this->register_endpoint( '/related' ); + $this->register_endpoint( '/related', null, true ); } /** @@ -55,14 +55,4 @@ static function( stdClass $item ) { return $result; } - - /** - * Determines if there are enough permissions to call the endpoint. - * - * @return bool - */ - public function permission_callback(): bool { - // Unauthenticated. - return true; - } } diff --git a/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php b/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php index b66996337..f7beda628 100644 --- a/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php +++ b/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php @@ -68,6 +68,23 @@ public function test_do_not_register_route_when_proxy_is_disabled(): void { parent::test_do_not_register_route_when_proxy_is_disabled(); } + /** + * Verifies forbidden error when current user doesn't have proper capabilities. + * + * @covers \Parsely\Endpoints\Base_API_Proxy::permission_callback + * + * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint + * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::register_endpoint + */ + public function test_access_of_analytics_posts_endpoint_is_forbidden(): void { + $response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', self::$route ) ); + $error = $response->as_error(); + + self::assertSame( 401, $response->get_status() ); + self::assertSame( 'rest_forbidden', $error->get_error_code() ); + self::assertSame( 'Sorry, you are not allowed to do that.', $error->get_error_message() ); + } + /** * Verifies that calling `GET /wp-parsely/v1/analytics/posts` returns an * error and does not perform a remote call when the apikey is not populated @@ -85,6 +102,7 @@ public function test_do_not_register_route_when_proxy_is_disabled(): void { * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint */ public function test_get_items_fails_without_apikey_set() { + $this->set_admin_user(); parent::test_get_items_fails_without_apikey_set(); } @@ -112,6 +130,7 @@ public function test_get_items_fails_without_apikey_set() { public function test_get_items() { TestCase::set_options( array( 'apikey' => 'example.com' ) ); TestCase::set_options( array( 'api_secret' => 'test' ) ); + $this->set_admin_user(); $dispatched = 0; $date_format = get_option( 'date_format' ); diff --git a/tests/Integration/TestCase.php b/tests/Integration/TestCase.php index 4abe9991a..da166b843 100644 --- a/tests/Integration/TestCase.php +++ b/tests/Integration/TestCase.php @@ -165,4 +165,14 @@ public function go_to_new_post(): int { return $post_id; } + + /** + * Sets current user as admin. + * + * @param int $admin_user_id User ID for the site administrator. + * Default is 1 which is assigned to first admin user while creating the site. + */ + public function set_admin_user( $admin_user_id = 1 ): void { + wp_set_current_user( $admin_user_id ); + } } diff --git a/tests/e2e/utils.js b/tests/e2e/utils.js index 7cedcffbf..db110176e 100644 --- a/tests/e2e/utils.js +++ b/tests/e2e/utils.js @@ -10,7 +10,7 @@ import { visitAdminPage, } from '@wordpress/e2e-test-utils'; -export const PLUGIN_VERSION = '3.5.2'; +export const PLUGIN_VERSION = '3.5.3'; export const waitForWpAdmin = () => page.waitForSelector( 'body.wp-admin' ); diff --git a/wp-parsely.php b/wp-parsely.php index 9e7b9ea16..0526cc001 100644 --- a/wp-parsely.php +++ b/wp-parsely.php @@ -11,7 +11,7 @@ * Plugin Name: Parse.ly * Plugin URI: https://www.parse.ly/help/integration/wordpress * Description: This plugin makes it a snap to add Parse.ly tracking code and metadata to your WordPress blog. - * Version: 3.5.2 + * Version: 3.5.3 * Author: Parse.ly * Author URI: https://www.parse.ly * Text Domain: wp-parsely @@ -52,7 +52,7 @@ return; } -const PARSELY_VERSION = '3.5.2'; +const PARSELY_VERSION = '3.5.3'; const PARSELY_FILE = __FILE__; require __DIR__ . '/src/class-parsely.php';