diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4303b..5b0e329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ All notable changes to `wp-rest-guard` will be documented in this file. -## v1.2.1 - 2024-02-26 +## v1.3.0 - 2024-02-27 -- Allow the claims to be added to added to a generated JWT via filter. +- Allow the claims to be added to a generated JWT via filter. +- Don't check `OPTIONS` requests by default. ## v1.2.0 - 2024-02-22 diff --git a/README.md b/README.md index 573f177..2cb4864 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # REST API Guard -Stable tag: 1.2.1 +Stable tag: 1.3.0 Requires at least: 6.0 diff --git a/plugin.php b/plugin.php index 37d1098..2392563 100644 --- a/plugin.php +++ b/plugin.php @@ -3,9 +3,9 @@ * Plugin Name: REST API Guard * Plugin URI: https://github.com/alleyinteractive/wp-rest-api-guard * Description: Restrict and control access to the REST API - * Version: 1.2.0 + * Version: 1.3.0 * Author: Sean Fisher - * Author URI: https://alley.co/ + * Author URI: https://alley.com/ * Requires at least: 6.0 * Tested up to: 6.3 * @@ -59,6 +59,18 @@ function should_prevent_anonymous_access( WP_REST_Server $server, WP_REST_Reques $settings = []; } + /** + * Filters whether the REST API Guard should check OPTIONS requests. + * + * This is useful for CORS preflight requests. + * + * @param bool $check Whether to check OPTIONS requests. Default false. + * @param \WP_REST_Request $request REST API Request. + */ + if ( 'OPTIONS' === $request->get_method() && ! apply_filters( 'rest_api_guard_check_options_requests', $settings['check_options_requests'] ?? false, $request ) ) { + return false; + } + if ( class_exists( JWT::class ) ) { /** * Check if the anonymous request requires a JSON Web Token (JWT). @@ -149,9 +161,10 @@ function should_prevent_anonymous_access( WP_REST_Server $server, WP_REST_Reques /** * Prevent access to the root of the REST API. * - * @param bool $prevent Whether to prevent anonymous access, default false. + * @param bool $prevent Whether to allow anonymous access to the REST API index. Default false. + * @param string $endpoint The endpoint of the request. */ - if ( '/' === $endpoint && false === apply_filters( 'rest_api_guard_allow_index_access', $settings['allow_index_access'] ?? false ) ) { + if ( '/' === $endpoint && false === apply_filters( 'rest_api_guard_allow_index_access', $settings['allow_index_access'] ?? false, $endpoint ) ) { return true; } @@ -171,9 +184,10 @@ function should_prevent_anonymous_access( WP_REST_Server $server, WP_REST_Reques /** * Prevent access to the /wp/v2/users endpoints by default. * - * @param bool $pre Whether to prevent access to the /wp/v2/users endpoints. + * @param bool $pre Whether to allow access to the /wp/v2/users endpoints. + * @param string $endpoint The endpoint of the request. */ - if ( preg_match( '#^/wp/v\d+/users($|/)#', $endpoint ) && false === apply_filters( 'rest_api_guard_allow_user_access', $settings['allow_user_access'] ?? false ) ) { + if ( preg_match( '#^/wp/v\d+/users($|/)#', $endpoint ) && false === apply_filters( 'rest_api_guard_allow_user_access', $settings['allow_user_access'] ?? false, $endpoint ) ) { return true; } diff --git a/readme.txt b/readme.txt index 0f8f867..c5c4f3d 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ === REST API Guard === -Stable tag: 1.2.1 +Stable tag: 1.3.0 Requires at least: 6.0 Tested up to: 6.3 Requires PHP: 8.0 diff --git a/settings.php b/settings.php index 0279dca..dba3528 100644 --- a/settings.php +++ b/settings.php @@ -145,6 +145,21 @@ function on_admin_init() { ], ); + add_settings_field( + 'check_options_requests', + __( 'Apply checks to OPTIONS requests', 'rest-api-guard' ), + __NAMESPACE__ . '\render_field', + SETTINGS_KEY, + SETTINGS_KEY, + [ + 'description' => __( 'Apply the same checks to OPTIONS requests as other requests.', 'rest-api-guard' ), + 'additional' => __( 'By default, the plugin will not apply any checks to OPTIONS requests. This setting will force the plugin to apply the same checks to OPTIONS requests as other requests. For CORS requests, this may need to be disabled to allow authentication with a JWT.', 'rest-api-guard' ), + 'filter' => 'rest_api_guard_check_options_requests', + 'id' => 'check_options_requests', + 'type' => 'checkbox', + ], + ); + add_settings_field( 'anonymous_requests_allowlist', __( 'Anonymous Request Allowlist', 'rest-api-guard' ), @@ -232,6 +247,7 @@ function sanitize_settings( $input ) { 'allow_index_access' => ! empty( $input['allow_index_access'] ), 'allow_namespace_access' => ! empty( $input['allow_namespace_access'] ), 'allow_user_access' => ! empty( $input['allow_user_access'] ), + 'check_options_requests' => ! empty( $input['check_options_requests'] ), 'anonymous_requests_allowlist' => ! empty( $input['anonymous_requests_allowlist'] ) ? sanitize_textarea_field( $input['anonymous_requests_allowlist'] ) : '', 'anonymous_requests_denylist' => ! empty( $input['anonymous_requests_denylist'] ) ? sanitize_textarea_field( $input['anonymous_requests_denylist'] ) : '', 'authentication_jwt' => ! empty( $input['authentication_jwt'] ), diff --git a/tests/RestApiGuardTest.php b/tests/RestApiGuardTest.php index 2d87c2d..6bd3463 100644 --- a/tests/RestApiGuardTest.php +++ b/tests/RestApiGuardTest.php @@ -121,6 +121,42 @@ public function test_prevent_anonymous_access_settings() { $this->get( rest_url( '/wp/v2/tags' ) )->assertUnauthorized(); } + public function test_check_options_requests() { + $this->expectApplied( 'rest_api_guard_check_options_requests' )->times( 3 ); + + // Check the default settings. + update_option( + SETTINGS_KEY, + [ + 'prevent_anonymous_access' => true, + ] + ); + + $this->options( rest_url( '/wp/v2/categories' ) )->assertOk(); + $this->get( rest_url( '/wp/v2/categories' ) )->assertUnauthorized(); + + update_option( + SETTINGS_KEY, + [ + 'prevent_anonymous_access' => true, + 'check_options_requests' => true, + ] + ); + + $this->options( rest_url( '/wp/v2/categories' ) )->assertUnauthorized(); + + update_option( + SETTINGS_KEY, + [ + 'prevent_anonymous_access' => true, + ] + ); + + add_filter( 'rest_api_guard_check_options_requests', fn () => true ); + + $this->options( rest_url( '/wp/v2/categories' ) )->assertUnauthorized(); + } + public function test_prevent_access_allowlist_code() { $this->get( rest_url( '/wp/v2/categories' ) )->assertOk();