Changeset 3040295
- Timestamp:
- 02/23/2024 02:39:13 PM (5 months ago)
- Location:
- rest-api-guard
- Files:
-
- 2 deleted
- 26 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
rest-api-guard/tags/1.2.0/.distignore
r3021965 r3040295 1 1 2 .DS_Store 2 3 Thumbs.db … … 21 22 Makefile 22 23 .github 24 -
rest-api-guard/tags/1.2.0/.gitignore
r3021965 r3040295 1 1 2 .DS_Store 2 3 Thumbs.db -
rest-api-guard/tags/1.2.0/CHANGELOG.md
r3021965 r3040295 2 2 3 3 All notable changes to `wp-rest-guard` will be documented in this file. 4 5 6 7 8 9 4 10 5 11 ## v1.1.1 - 2024-01-15 -
rest-api-guard/tags/1.2.0/README.md
r3021965 r3040295 1 1 # REST API Guard 2 2 3 Stable tag: 1. 1.23 Stable tag: 1. 4 4 5 5 Requires at least: 6.0 … … 57 57 ### Preventing Access to Index (`/`) or Namespace Endpoints (`wp/v2`) 58 58 59 To prevent anonymous users from brow ing your site and discovering what plugins/post types are setup, the plugin restricts access to the index (`/`) and namespace (`wp/v2`) endpoints. This can be prevented in the plugin's settings or via code:59 To prevent anonymous users from browup, the plugin restricts access to the index (`/`) and namespace (`wp/v2`) endpoints. This can be prevented in the plugin's settings or via code: 60 60 61 61 ```php … … 119 119 ``` 120 120 121 ### Require JSON Web Token (JWT) Authentication 121 ### Require JSON Web Token (JWT) Authentication 122 122 123 123 Anonymous users can be required to authenticate via a JSON Web Token (JWT) to 124 access the REST API. This can be configured in the plugin's settings or via125 code:124 access the REST API. 125 code: 126 126 127 127 ```php … … 135 135 136 136 ```php 137 add_filter( 138 'rest_api_guard_jwt_audience', 139 function ( string $audience ): string { 140 return 'custom-audience'; 141 } 142 ); 137 add_filter( 'rest_api_guard_jwt_audience', fn ( string $audience ) => 'custom-audience' ); 143 138 144 add_filter( 145 'rest_api_guard_jwt_issuer', 146 function ( string $issuer ): string { 147 return 'https://example.com'; 148 } 149 ); 139 add_filter( 'rest_api_guard_jwt_issuer', fn ( string $issuer ) => 'https://example.com' ); 150 140 ``` 151 141 … … 154 144 155 145 ```php 156 add_filter( 157 'rest_api_guard_jwt_secret', 158 function ( string $secret ): string { 159 return 'my-custom-secret'; 160 } 146 add_filter( 'rest_api_guard_jwt_secret', fn ( string $secret ) => 'my-custom-secret' ); 147 ``` 148 149 ### Allow JWT Authentication for Authenticated Users 150 151 Authenticated users can be authenticated with the REST API via a JSON Web Token. 152 Similar to the anonymous JWT authentication, users should pass an 153 `Authorization: Bearer <token>` header with their request. This can be 154 configured in the plugin's settings or via code: 155 156 ```php 157 add_filter( 'rest_api_guard_user_authentication_jwt', fn () => true ); 158 ``` 159 160 ### Generating JWTs for Anonymous and Authenticated Users 161 162 JWTs can be generated by calling the `wp rest-api-guard generate-jwt [--user=<user_id>]` 163 command or using the `Alley\WP\REST_API_Guard\generate_jwt()` method: 164 165 ```php 166 $jwt = \Alley\WP\REST_API_Guard\generate_jwt( 167 expiration: 3600, // Optional. The expiration time in seconds from now. 168 user: 1, // Optional. The user ID to generate the JWT for. Supports `WP_User` or user ID. 161 169 ); 162 170 ``` 163 164 You can generate a JWT for use with the REST API by calling the165 `wp rest-api-guard generate-jwt` command.166 171 167 172 ## Testing -
rest-api-guard/tags/1.2.0/cli.php
r3021965 r3040295 10 10 WP_CLI::add_command( 11 11 'rest-api-guard generate-jwt', 12 function () { 13 echo generate_jwt() . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 12 function ( $args, $assoc_args ) { 13 $expiration = isset( $assoc_args['expiration'] ) ? (int) $assoc_args['expiration'] : null; 14 $user = isset( $assoc_args['user'] ) ? (int) $assoc_args['user'] : null; 15 16 echo generate_jwt( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 17 expiration: $expiration, 18 user: $user, 19 ) . PHP_EOL; 14 20 }, 15 21 [ 16 22 'shortdesc' => __( 'Generate a JSON Web Token (JWT).', 'rest-api-guard' ), 17 ] 23 'synopsis' => '[--expiration=<expiration>] [--user=<user>]', 24 ], 18 25 ); -
rest-api-guard/tags/1.2.0/composer.json
r3021965 r3040295 24 24 "alleyinteractive/alley-coding-standards": "^2.0", 25 25 "alleyinteractive/composer-wordpress-autoloader": "^1.0", 26 "mantle-framework/testkit": "^0.7", 27 "nunomaduro/collision": "^5.0" 26 "mantle-framework/testkit": "^1.0" 28 27 }, 29 28 "config": { … … 32 31 "dealerdirect/phpcodesniffer-composer-installer": true, 33 32 "pestphp/pest-plugin": true 34 },35 "platform": {36 "php": "8.0"37 33 }, 38 34 "sort-packages": true -
rest-api-guard/tags/1.2.0/plugin.php
r3021965 r3040295 4 4 * Plugin URI: https://github.com/alleyinteractive/wp-rest-api-guard 5 5 * Description: Restrict and control access to the REST API 6 * Version: 1. 1.26 * Version: 1. 7 7 * Author: Sean Fisher 8 8 * Author URI: https://alley.co/ … … 24 24 use WP_REST_Request; 25 25 use WP_REST_Server; 26 26 27 27 28 if ( ! defined( 'ABSPATH' ) ) { … … 59 60 } 60 61 61 /** 62 * Check if the anonymous request requires a JSON Web Token (JWT). 63 * 64 * @param bool $require Whether to require a JWT, default false. 65 * @param \WP_REST_Request $request REST API Request. 66 */ 67 if ( class_exists( JWT::class ) && true === apply_filters( 'rest_api_guard_authentication_jwt', $settings['authentication_jwt'] ?? false, $request ) ) { 68 try { 69 $jwt = $request->get_header( 'Authorization' ); 70 71 if ( empty( $jwt ) ) { 72 throw new InvalidArgumentException( __( 'No authorization header was found.', 'rest-api-guard' ) ); 73 } 74 75 if ( 0 !== strpos( $jwt, 'Bearer ' ) ) { 76 throw new InvalidArgumentException( __( 'Invalid authorization header.', 'rest-api-guard' ) ); 77 } 78 79 $decoded = JWT::decode( 80 substr( $jwt, 7 ), 81 new Key( get_jwt_secret(), 'HS256' ), 82 ); 83 84 // Verify the contents of the JWT. 85 if ( empty( $decoded->iss ) || get_jwt_issuer() !== $decoded->iss ) { 86 throw new InvalidArgumentException( __( 'Invalid JWT issuer.', 'rest-api-guard' ) ); 87 } 88 89 if ( empty( $decoded->aud ) || get_jwt_audience() !== $decoded->aud ) { 90 throw new InvalidArgumentException( __( 'Invalid JWT audience.', 'rest-api-guard' ) ); 91 } 92 } catch ( \Exception $error ) { 93 return new WP_Error( 94 'rest_api_guard_unauthorized', 95 /** 96 * Filter the authorization error message. 97 * 98 * @param string $message The error message. 99 * @param \Throwable $error The error that occurred. 100 */ 101 apply_filters( 102 'rest_api_guard_invalid_jwt_message', 103 __( 'Invalid authorization header.', 'rest-api-guard' ), 104 $error, 105 ), 106 [ 107 'status' => rest_authorization_required_code(), 108 ] 109 ); 62 if ( class_exists( JWT::class ) ) { 63 /** 64 * Check if the anonymous request requires a JSON Web Token (JWT). 65 * 66 * @param bool $require Whether to require a JWT, default false. 67 * @param \WP_REST_Request $request REST API Request. 68 */ 69 $require_anonymous_jwt = true === apply_filters( 'rest_api_guard_authentication_jwt', $settings['authentication_jwt'] ?? false, $request ); 70 $allow_user_jwt = true === apply_filters( 'rest_api_guard_user_authentication_jwt', $settings['user_authentication_jwt'] ?? false, $request ); 71 72 if ( $require_anonymous_jwt || $allow_user_jwt ) { 73 try { 74 $jwt = $request->get_header( 'Authorization' ); 75 76 if ( empty( $jwt ) && $require_anonymous_jwt ) { 77 throw new InvalidArgumentException( __( 'No authorization header token was found and is required for this request.', 'rest-api-guard' ) ); 78 } 79 80 if ( ! empty( $jwt ) ) { 81 if ( 0 !== strpos( $jwt, 'Bearer ' ) ) { 82 throw new InvalidArgumentException( __( 'Invalid authorization header.', 'rest-api-guard' ) ); 83 } 84 85 $decoded = JWT::decode( 86 substr( $jwt, 7 ), 87 new Key( get_jwt_secret(), 'HS256' ), 88 ); 89 90 // Verify the contents of the JWT. 91 if ( empty( $decoded->iss ) || get_jwt_issuer() !== $decoded->iss ) { 92 throw new InvalidArgumentException( __( 'Invalid JWT issuer.', 'rest-api-guard' ) ); 93 } 94 95 if ( empty( $decoded->aud ) || get_jwt_audience() !== $decoded->aud ) { 96 throw new InvalidArgumentException( __( 'Invalid JWT audience.', 'rest-api-guard' ) ); 97 } 98 99 if ( $allow_user_jwt && ! empty( $decoded->sub ) ) { 100 $user = get_user_by( 'id', $decoded->sub ); 101 102 if ( ! $user instanceof WP_User ) { 103 throw new InvalidArgumentException( __( 'Invalid user in JWT sub.', 'rest-api-guard' ) ); 104 } 105 106 wp_set_current_user( $user->ID ); 107 108 return false; 109 } 110 } 111 } catch ( \Exception $error ) { 112 return new WP_Error( 113 'rest_api_guard_unauthorized', 114 /** 115 * Filter the authorization error message. 116 * 117 * @param string $message The error message being returned. 118 * @param \Throwable $error The error that occurred. 119 */ 120 apply_filters( 121 'rest_api_guard_invalid_jwt_message', 122 sprintf( 123 /* translators: %s: The error message. */ 124 __( 'Error authentication with token: %s', 'rest-api-guard' ), 125 $error->getMessage(), 126 ), 127 $error, 128 ), 129 [ 130 'status' => rest_authorization_required_code(), 131 ] 132 ); 133 } 110 134 } 111 135 } … … 284 308 // Generate the JWT secret if it does not exist. 285 309 if ( empty( get_option( 'rest_api_guard_jwt_secret' ) ) ) { 286 update_option( 'rest_api_guard_jwt_secret', wp_generate_password( 12, false ) );310 update_option( 'rest_api_guard_jwt_secret', wp_generate_password( 2, false ) ); 287 311 } 288 312 … … 298 322 * Generate a JSON Web Token (JWT). 299 323 * 324 325 326 327 300 328 * @return string 301 */ 302 function generate_jwt(): string { 303 return JWT::encode( 304 [ 305 'iss' => get_jwt_issuer(), 306 'aud' => get_jwt_audience(), 307 'iat' => time(), 308 ], 309 get_jwt_secret(), 310 'HS256' 311 ); 329 * 330 * @throws InvalidArgumentException If the user is invalid or unknown. 331 */ 332 function generate_jwt( ?int $expiration = null, WP_User|int|null $user = null ): string { 333 $payload = [ 334 'iss' => get_jwt_issuer(), 335 'aud' => get_jwt_audience(), 336 'iat' => time(), 337 ]; 338 339 if ( null !== $expiration ) { 340 $payload['exp'] = time() + $expiration; 341 } 342 343 if ( null !== $user ) { 344 $user = $user instanceof WP_User ? $user : get_user_by( 'id', $user ); 345 346 if ( ! $user instanceof WP_User ) { 347 throw new InvalidArgumentException( esc_html__( 'Invalid user.', 'rest-api-guard' ) ); 348 } 349 350 $payload['sub'] = $user->ID; 351 $payload['user_login'] = $user->user_login; 352 } 353 354 return JWT::encode( $payload, get_jwt_secret(), 'HS256' ); 312 355 } 313 356 -
rest-api-guard/tags/1.2.0/readme.txt
r3021965 r3040295 1 1 === REST API Guard === 2 Stable tag: 1. 1.22 Stable tag: 1. 3 3 Requires at least: 6.0 4 4 Tested up to: 6.3 … … 72 72 ### Restrict Anonymous Access to Specific Namespaces/Routes (Denylist) 73 73 74 Anonymous users can be restricted from specific namespaces/routes. This acts as a denylist for specific paths that an anonymous user cannot access. The paths support regular expressions for matching. The use of the [Allowlist](#limit-anonymous-access-to-specific-namespacesroutes-allowlist) takes priority over this denylist. This can be configured in the plugin's settings or via code: 74 Anonymous users can be restricted from specific namespaces/routes. This acts as 75 a denylist for specific paths that an anonymous user cannot access. The paths 76 support regular expressions for matching. The use of the allowlist takes 77 priority over this denylist. This can be configured in the plugin's settings or 78 via code: 75 79 76 80 add_filter( … … 89 93 90 94 Anonymous users can be required to authenticate via a JSON Web Token (JWT) to 91 access the REST API. This can be configured in the plugin's settings or via92 code:95 access the REST API. 96 code: 93 97 94 ```php 95 add_filter( 'rest_api_guard_authentication_jwt', fn () => true ); 96 ``` 98 add_filter( 'rest_api_guard_authentication_jwt', fn () => true ); 97 99 98 100 Out of the box, the plugin will look for a JWT in the `Authorization: Bearer 99 <token>` header. The JWT will be expected to have an audience of 'wordpress-rest-api' and issuer of the site's URL. This can be configured in the plugin's settings or via code: 101 <token>` header. The JWT will be expected to have an audience of 102 'wordpress-rest-api' and issuer of the site's URL. This can be configured in the 103 plugin's settings or via code: 100 104 101 ```php 102 add_filter( 103 'rest_api_guard_jwt_audience', 104 function ( string $audience ): string { 105 return 'custom-audience'; 106 } 107 ); 105 add_filter( 106 'rest_api_guard_jwt_audience', 107 function ( string $audience ): string { 108 return 'custom-audience'; 109 } 110 ); 108 111 109 add_filter( 110 'rest_api_guard_jwt_issuer', 111 function ( string $issuer ): string { 112 return 'https://example.com'; 113 } 114 ); 115 ``` 112 add_filter( 113 'rest_api_guard_jwt_issuer', 114 function ( string $issuer ): string { 115 return 'https://example.com'; 116 } 117 ); 116 118 117 119 The JWT's secret will be autogenerated and stored in the database in the 118 120 `rest_api_guard_jwt_secret` option. The secret can also be changed via code: 119 121 120 ```php 121 add_filter( 122 'rest_api_guard_jwt_secret', 123 function ( string $secret ): string { 124 return 'my-custom-secret'; 125 } 126 ); 127 ``` 122 add_filter( 123 'rest_api_guard_jwt_secret', 124 function ( string $secret ): string { 125 return 'my-custom-secret'; 126 } 127 ); 128 128 129 You can generate a JWT for use with the REST API by calling the 130 `wp rest-api-guard generate-jwt` command. 129 ### Allow JWT Authentication for Authenticated Users 130 131 Authenticated users can be authenticated with the REST API via a JSON Web Token. 132 Similar to the anonymous JWT authentication, users should pass an 133 `Authorization: Bearer <token>` header with their request. This can be 134 configured in the plugin's settings or via code: 135 136 add_filter( 'rest_api_guard_user_authentication_jwt', fn () => true ); 137 138 ### Generating JWTs for Anonymous and Authenticated Users 139 140 JWTs can be generated by calling the 141 `wp rest-api-guard generate-jwt [--user=<user_id>]` command or using the 142 `Alley\WP\REST_API_Guard\generate_jwt()` method: 143 144 $jwt = \Alley\WP\REST_API_Guard\generate_jwt( 145 expiration: 3600, // Optional. The expiration time in seconds from now. 146 user: 1, // Optional. The user ID to generate the JWT for. Supports `WP_User` or user ID. 147 ); -
rest-api-guard/tags/1.2.0/settings.php
r3021965 r3040295 28 28 */ 29 29 function on_admin_menu() { 30 31 32 33 34 35 36 37 38 30 39 add_options_page( 31 40 __( 'REST API Guard', 'rest-api-guard' ), … … 176 185 'additional' => sprintf( 177 186 /* translators: 1: The JWT audience. 2: The JWT issuer. */ 178 __( 'When enabled, the plugin will require anonymous users to pass an "Authorization: Bearer <token>" with the token being a valid JSON Web Token (JWT). The plugin will be expecting a JWT with an audience of "%1$s", issuer of "%2$s", and secret that matches the value of the "rest_api_guard_jwt_secret" option. ', 'rest-api-guard' ),187 __( 'When enabled, the plugin will require anonymous users to pass an "Authorization: Bearer <token>" with the token being a valid JSON Web Token (JWT). The plugin will be expecting a JWT with an audience of "%1$s", issuer of "%2$s", and secret that matches the value of the "rest_api_guard_jwt_secret" option.', 'rest-api-guard' ), 179 188 get_jwt_audience(), 180 189 get_jwt_issuer(), … … 185 194 ], 186 195 ); 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 187 216 } 188 217 } … … 206 235 'anonymous_requests_allowlist' => ! empty( $input['anonymous_requests_allowlist'] ) ? sanitize_textarea_field( $input['anonymous_requests_allowlist'] ) : '', 207 236 'anonymous_requests_denylist' => ! empty( $input['anonymous_requests_denylist'] ) ? sanitize_textarea_field( $input['anonymous_requests_denylist'] ) : '', 237 238 208 239 ]; 209 240 } -
rest-api-guard/tags/1.2.0/vendor/autoload.php
r3021965 r3040295 23 23 require_once __DIR__ . '/composer/autoload_real.php'; 24 24 25 return ComposerAutoloaderInit b3808e08bff289327297fb981027fb31::getLoader();25 return ComposerAutoloaderInit::getLoader(); -
rest-api-guard/tags/1.2.0/vendor/composer/autoload_real.php
r3021965 r3040295 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit b3808e08bff289327297fb981027fb315 class ComposerAutoloaderInit 6 6 { 7 7 private static $loader; … … 25 25 require __DIR__ . '/platform_check.php'; 26 26 27 spl_autoload_register(array('ComposerAutoloaderInit b3808e08bff289327297fb981027fb31', 'loadClassLoader'), true, true);27 spl_autoload_register(array('ComposerAutoloaderInit', 'loadClassLoader'), true, true); 28 28 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); 29 spl_autoload_unregister(array('ComposerAutoloaderInit b3808e08bff289327297fb981027fb31', 'loadClassLoader'));29 spl_autoload_unregister(array('ComposerAutoloaderInit', 'loadClassLoader')); 30 30 31 31 require __DIR__ . '/autoload_static.php'; 32 call_user_func(\Composer\Autoload\ComposerStaticInit b3808e08bff289327297fb981027fb31::getInitializer($loader));32 call_user_func(\Composer\Autoload\ComposerStaticInit::getInitializer($loader)); 33 33 34 34 $loader->register(true); -
rest-api-guard/tags/1.2.0/vendor/composer/autoload_static.php
r3021965 r3040295 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit b3808e08bff289327297fb981027fb317 class ComposerStaticInit 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 28 28 { 29 29 return \Closure::bind(function () use ($loader) { 30 $loader->prefixLengthsPsr4 = ComposerStaticInit b3808e08bff289327297fb981027fb31::$prefixLengthsPsr4;31 $loader->prefixDirsPsr4 = ComposerStaticInit b3808e08bff289327297fb981027fb31::$prefixDirsPsr4;32 $loader->classMap = ComposerStaticInit b3808e08bff289327297fb981027fb31::$classMap;30 $loader->prefixLengthsPsr4 = ComposerStaticInit::$prefixLengthsPsr4; 31 $loader->prefixDirsPsr4 = ComposerStaticInit::$prefixDirsPsr4; 32 $loader->classMap = ComposerStaticInit::$classMap; 33 33 34 34 }, null, ClassLoader::class); -
rest-api-guard/tags/1.2.0/vendor/composer/installed.php
r3021965 r3040295 4 4 'pretty_version' => '1.0.0+no-version-set', 5 5 'version' => '1.0.0.0', 6 'reference' => NULL,6 'reference' => , 7 7 'type' => 'wordpress-plugin', 8 8 'install_path' => __DIR__ . '/../../', … … 14 14 'pretty_version' => '1.0.0+no-version-set', 15 15 'version' => '1.0.0.0', 16 'reference' => NULL,16 'reference' => , 17 17 'type' => 'wordpress-plugin', 18 18 'install_path' => __DIR__ . '/../../', -
rest-api-guard/trunk/.distignore
r3021965 r3040295 1 1 2 .DS_Store 2 3 Thumbs.db … … 21 22 Makefile 22 23 .github 24 -
rest-api-guard/trunk/.gitignore
r3021965 r3040295 1 1 2 .DS_Store 2 3 Thumbs.db -
rest-api-guard/trunk/CHANGELOG.md
r3021965 r3040295 2 2 3 3 All notable changes to `wp-rest-guard` will be documented in this file. 4 5 6 7 8 9 4 10 5 11 ## v1.1.1 - 2024-01-15 -
rest-api-guard/trunk/README.md
r3021965 r3040295 1 1 # REST API Guard 2 2 3 Stable tag: 1. 1.23 Stable tag: 1. 4 4 5 5 Requires at least: 6.0 … … 57 57 ### Preventing Access to Index (`/`) or Namespace Endpoints (`wp/v2`) 58 58 59 To prevent anonymous users from brow ing your site and discovering what plugins/post types are setup, the plugin restricts access to the index (`/`) and namespace (`wp/v2`) endpoints. This can be prevented in the plugin's settings or via code:59 To prevent anonymous users from browup, the plugin restricts access to the index (`/`) and namespace (`wp/v2`) endpoints. This can be prevented in the plugin's settings or via code: 60 60 61 61 ```php … … 119 119 ``` 120 120 121 ### Require JSON Web Token (JWT) Authentication 121 ### Require JSON Web Token (JWT) Authentication 122 122 123 123 Anonymous users can be required to authenticate via a JSON Web Token (JWT) to 124 access the REST API. This can be configured in the plugin's settings or via125 code:124 access the REST API. 125 code: 126 126 127 127 ```php … … 135 135 136 136 ```php 137 add_filter( 138 'rest_api_guard_jwt_audience', 139 function ( string $audience ): string { 140 return 'custom-audience'; 141 } 142 ); 137 add_filter( 'rest_api_guard_jwt_audience', fn ( string $audience ) => 'custom-audience' ); 143 138 144 add_filter( 145 'rest_api_guard_jwt_issuer', 146 function ( string $issuer ): string { 147 return 'https://example.com'; 148 } 149 ); 139 add_filter( 'rest_api_guard_jwt_issuer', fn ( string $issuer ) => 'https://example.com' ); 150 140 ``` 151 141 … … 154 144 155 145 ```php 156 add_filter( 157 'rest_api_guard_jwt_secret', 158 function ( string $secret ): string { 159 return 'my-custom-secret'; 160 } 146 add_filter( 'rest_api_guard_jwt_secret', fn ( string $secret ) => 'my-custom-secret' ); 147 ``` 148 149 ### Allow JWT Authentication for Authenticated Users 150 151 Authenticated users can be authenticated with the REST API via a JSON Web Token. 152 Similar to the anonymous JWT authentication, users should pass an 153 `Authorization: Bearer <token>` header with their request. This can be 154 configured in the plugin's settings or via code: 155 156 ```php 157 add_filter( 'rest_api_guard_user_authentication_jwt', fn () => true ); 158 ``` 159 160 ### Generating JWTs for Anonymous and Authenticated Users 161 162 JWTs can be generated by calling the `wp rest-api-guard generate-jwt [--user=<user_id>]` 163 command or using the `Alley\WP\REST_API_Guard\generate_jwt()` method: 164 165 ```php 166 $jwt = \Alley\WP\REST_API_Guard\generate_jwt( 167 expiration: 3600, // Optional. The expiration time in seconds from now. 168 user: 1, // Optional. The user ID to generate the JWT for. Supports `WP_User` or user ID. 161 169 ); 162 170 ``` 163 164 You can generate a JWT for use with the REST API by calling the165 `wp rest-api-guard generate-jwt` command.166 171 167 172 ## Testing -
rest-api-guard/trunk/cli.php
r3021965 r3040295 10 10 WP_CLI::add_command( 11 11 'rest-api-guard generate-jwt', 12 function () { 13 echo generate_jwt() . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 12 function ( $args, $assoc_args ) { 13 $expiration = isset( $assoc_args['expiration'] ) ? (int) $assoc_args['expiration'] : null; 14 $user = isset( $assoc_args['user'] ) ? (int) $assoc_args['user'] : null; 15 16 echo generate_jwt( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 17 expiration: $expiration, 18 user: $user, 19 ) . PHP_EOL; 14 20 }, 15 21 [ 16 22 'shortdesc' => __( 'Generate a JSON Web Token (JWT).', 'rest-api-guard' ), 17 ] 23 'synopsis' => '[--expiration=<expiration>] [--user=<user>]', 24 ], 18 25 ); -
rest-api-guard/trunk/composer.json
r3021965 r3040295 24 24 "alleyinteractive/alley-coding-standards": "^2.0", 25 25 "alleyinteractive/composer-wordpress-autoloader": "^1.0", 26 "mantle-framework/testkit": "^0.7", 27 "nunomaduro/collision": "^5.0" 26 "mantle-framework/testkit": "^1.0" 28 27 }, 29 28 "config": { … … 32 31 "dealerdirect/phpcodesniffer-composer-installer": true, 33 32 "pestphp/pest-plugin": true 34 },35 "platform": {36 "php": "8.0"37 33 }, 38 34 "sort-packages": true -
rest-api-guard/trunk/plugin.php
r3021965 r3040295 4 4 * Plugin URI: https://github.com/alleyinteractive/wp-rest-api-guard 5 5 * Description: Restrict and control access to the REST API 6 * Version: 1. 1.26 * Version: 1. 7 7 * Author: Sean Fisher 8 8 * Author URI: https://alley.co/ … … 24 24 use WP_REST_Request; 25 25 use WP_REST_Server; 26 26 27 27 28 if ( ! defined( 'ABSPATH' ) ) { … … 59 60 } 60 61 61 /** 62 * Check if the anonymous request requires a JSON Web Token (JWT). 63 * 64 * @param bool $require Whether to require a JWT, default false. 65 * @param \WP_REST_Request $request REST API Request. 66 */ 67 if ( class_exists( JWT::class ) && true === apply_filters( 'rest_api_guard_authentication_jwt', $settings['authentication_jwt'] ?? false, $request ) ) { 68 try { 69 $jwt = $request->get_header( 'Authorization' ); 70 71 if ( empty( $jwt ) ) { 72 throw new InvalidArgumentException( __( 'No authorization header was found.', 'rest-api-guard' ) ); 73 } 74 75 if ( 0 !== strpos( $jwt, 'Bearer ' ) ) { 76 throw new InvalidArgumentException( __( 'Invalid authorization header.', 'rest-api-guard' ) ); 77 } 78 79 $decoded = JWT::decode( 80 substr( $jwt, 7 ), 81 new Key( get_jwt_secret(), 'HS256' ), 82 ); 83 84 // Verify the contents of the JWT. 85 if ( empty( $decoded->iss ) || get_jwt_issuer() !== $decoded->iss ) { 86 throw new InvalidArgumentException( __( 'Invalid JWT issuer.', 'rest-api-guard' ) ); 87 } 88 89 if ( empty( $decoded->aud ) || get_jwt_audience() !== $decoded->aud ) { 90 throw new InvalidArgumentException( __( 'Invalid JWT audience.', 'rest-api-guard' ) ); 91 } 92 } catch ( \Exception $error ) { 93 return new WP_Error( 94 'rest_api_guard_unauthorized', 95 /** 96 * Filter the authorization error message. 97 * 98 * @param string $message The error message. 99 * @param \Throwable $error The error that occurred. 100 */ 101 apply_filters( 102 'rest_api_guard_invalid_jwt_message', 103 __( 'Invalid authorization header.', 'rest-api-guard' ), 104 $error, 105 ), 106 [ 107 'status' => rest_authorization_required_code(), 108 ] 109 ); 62 if ( class_exists( JWT::class ) ) { 63 /** 64 * Check if the anonymous request requires a JSON Web Token (JWT). 65 * 66 * @param bool $require Whether to require a JWT, default false. 67 * @param \WP_REST_Request $request REST API Request. 68 */ 69 $require_anonymous_jwt = true === apply_filters( 'rest_api_guard_authentication_jwt', $settings['authentication_jwt'] ?? false, $request ); 70 $allow_user_jwt = true === apply_filters( 'rest_api_guard_user_authentication_jwt', $settings['user_authentication_jwt'] ?? false, $request ); 71 72 if ( $require_anonymous_jwt || $allow_user_jwt ) { 73 try { 74 $jwt = $request->get_header( 'Authorization' ); 75 76 if ( empty( $jwt ) && $require_anonymous_jwt ) { 77 throw new InvalidArgumentException( __( 'No authorization header token was found and is required for this request.', 'rest-api-guard' ) ); 78 } 79 80 if ( ! empty( $jwt ) ) { 81 if ( 0 !== strpos( $jwt, 'Bearer ' ) ) { 82 throw new InvalidArgumentException( __( 'Invalid authorization header.', 'rest-api-guard' ) ); 83 } 84 85 $decoded = JWT::decode( 86 substr( $jwt, 7 ), 87 new Key( get_jwt_secret(), 'HS256' ), 88 ); 89 90 // Verify the contents of the JWT. 91 if ( empty( $decoded->iss ) || get_jwt_issuer() !== $decoded->iss ) { 92 throw new InvalidArgumentException( __( 'Invalid JWT issuer.', 'rest-api-guard' ) ); 93 } 94 95 if ( empty( $decoded->aud ) || get_jwt_audience() !== $decoded->aud ) { 96 throw new InvalidArgumentException( __( 'Invalid JWT audience.', 'rest-api-guard' ) ); 97 } 98 99 if ( $allow_user_jwt && ! empty( $decoded->sub ) ) { 100 $user = get_user_by( 'id', $decoded->sub ); 101 102 if ( ! $user instanceof WP_User ) { 103 throw new InvalidArgumentException( __( 'Invalid user in JWT sub.', 'rest-api-guard' ) ); 104 } 105 106 wp_set_current_user( $user->ID ); 107 108 return false; 109 } 110 } 111 } catch ( \Exception $error ) { 112 return new WP_Error( 113 'rest_api_guard_unauthorized', 114 /** 115 * Filter the authorization error message. 116 * 117 * @param string $message The error message being returned. 118 * @param \Throwable $error The error that occurred. 119 */ 120 apply_filters( 121 'rest_api_guard_invalid_jwt_message', 122 sprintf( 123 /* translators: %s: The error message. */ 124 __( 'Error authentication with token: %s', 'rest-api-guard' ), 125 $error->getMessage(), 126 ), 127 $error, 128 ), 129 [ 130 'status' => rest_authorization_required_code(), 131 ] 132 ); 133 } 110 134 } 111 135 } … … 284 308 // Generate the JWT secret if it does not exist. 285 309 if ( empty( get_option( 'rest_api_guard_jwt_secret' ) ) ) { 286 update_option( 'rest_api_guard_jwt_secret', wp_generate_password( 12, false ) );310 update_option( 'rest_api_guard_jwt_secret', wp_generate_password( 2, false ) ); 287 311 } 288 312 … … 298 322 * Generate a JSON Web Token (JWT). 299 323 * 324 325 326 327 300 328 * @return string 301 */ 302 function generate_jwt(): string { 303 return JWT::encode( 304 [ 305 'iss' => get_jwt_issuer(), 306 'aud' => get_jwt_audience(), 307 'iat' => time(), 308 ], 309 get_jwt_secret(), 310 'HS256' 311 ); 329 * 330 * @throws InvalidArgumentException If the user is invalid or unknown. 331 */ 332 function generate_jwt( ?int $expiration = null, WP_User|int|null $user = null ): string { 333 $payload = [ 334 'iss' => get_jwt_issuer(), 335 'aud' => get_jwt_audience(), 336 'iat' => time(), 337 ]; 338 339 if ( null !== $expiration ) { 340 $payload['exp'] = time() + $expiration; 341 } 342 343 if ( null !== $user ) { 344 $user = $user instanceof WP_User ? $user : get_user_by( 'id', $user ); 345 346 if ( ! $user instanceof WP_User ) { 347 throw new InvalidArgumentException( esc_html__( 'Invalid user.', 'rest-api-guard' ) ); 348 } 349 350 $payload['sub'] = $user->ID; 351 $payload['user_login'] = $user->user_login; 352 } 353 354 return JWT::encode( $payload, get_jwt_secret(), 'HS256' ); 312 355 } 313 356 -
rest-api-guard/trunk/readme.txt
r3021965 r3040295 1 1 === REST API Guard === 2 Stable tag: 1. 1.22 Stable tag: 1. 3 3 Requires at least: 6.0 4 4 Tested up to: 6.3 … … 72 72 ### Restrict Anonymous Access to Specific Namespaces/Routes (Denylist) 73 73 74 Anonymous users can be restricted from specific namespaces/routes. This acts as a denylist for specific paths that an anonymous user cannot access. The paths support regular expressions for matching. The use of the [Allowlist](#limit-anonymous-access-to-specific-namespacesroutes-allowlist) takes priority over this denylist. This can be configured in the plugin's settings or via code: 74 Anonymous users can be restricted from specific namespaces/routes. This acts as 75 a denylist for specific paths that an anonymous user cannot access. The paths 76 support regular expressions for matching. The use of the allowlist takes 77 priority over this denylist. This can be configured in the plugin's settings or 78 via code: 75 79 76 80 add_filter( … … 89 93 90 94 Anonymous users can be required to authenticate via a JSON Web Token (JWT) to 91 access the REST API. This can be configured in the plugin's settings or via92 code:95 access the REST API. 96 code: 93 97 94 ```php 95 add_filter( 'rest_api_guard_authentication_jwt', fn () => true ); 96 ``` 98 add_filter( 'rest_api_guard_authentication_jwt', fn () => true ); 97 99 98 100 Out of the box, the plugin will look for a JWT in the `Authorization: Bearer 99 <token>` header. The JWT will be expected to have an audience of 'wordpress-rest-api' and issuer of the site's URL. This can be configured in the plugin's settings or via code: 101 <token>` header. The JWT will be expected to have an audience of 102 'wordpress-rest-api' and issuer of the site's URL. This can be configured in the 103 plugin's settings or via code: 100 104 101 ```php 102 add_filter( 103 'rest_api_guard_jwt_audience', 104 function ( string $audience ): string { 105 return 'custom-audience'; 106 } 107 ); 105 add_filter( 106 'rest_api_guard_jwt_audience', 107 function ( string $audience ): string { 108 return 'custom-audience'; 109 } 110 ); 108 111 109 add_filter( 110 'rest_api_guard_jwt_issuer', 111 function ( string $issuer ): string { 112 return 'https://example.com'; 113 } 114 ); 115 ``` 112 add_filter( 113 'rest_api_guard_jwt_issuer', 114 function ( string $issuer ): string { 115 return 'https://example.com'; 116 } 117 ); 116 118 117 119 The JWT's secret will be autogenerated and stored in the database in the 118 120 `rest_api_guard_jwt_secret` option. The secret can also be changed via code: 119 121 120 ```php 121 add_filter( 122 'rest_api_guard_jwt_secret', 123 function ( string $secret ): string { 124 return 'my-custom-secret'; 125 } 126 ); 127 ``` 122 add_filter( 123 'rest_api_guard_jwt_secret', 124 function ( string $secret ): string { 125 return 'my-custom-secret'; 126 } 127 ); 128 128 129 You can generate a JWT for use with the REST API by calling the 130 `wp rest-api-guard generate-jwt` command. 129 ### Allow JWT Authentication for Authenticated Users 130 131 Authenticated users can be authenticated with the REST API via a JSON Web Token. 132 Similar to the anonymous JWT authentication, users should pass an 133 `Authorization: Bearer <token>` header with their request. This can be 134 configured in the plugin's settings or via code: 135 136 add_filter( 'rest_api_guard_user_authentication_jwt', fn () => true ); 137 138 ### Generating JWTs for Anonymous and Authenticated Users 139 140 JWTs can be generated by calling the 141 `wp rest-api-guard generate-jwt [--user=<user_id>]` command or using the 142 `Alley\WP\REST_API_Guard\generate_jwt()` method: 143 144 $jwt = \Alley\WP\REST_API_Guard\generate_jwt( 145 expiration: 3600, // Optional. The expiration time in seconds from now. 146 user: 1, // Optional. The user ID to generate the JWT for. Supports `WP_User` or user ID. 147 ); -
rest-api-guard/trunk/settings.php
r3021965 r3040295 28 28 */ 29 29 function on_admin_menu() { 30 31 32 33 34 35 36 37 38 30 39 add_options_page( 31 40 __( 'REST API Guard', 'rest-api-guard' ), … … 176 185 'additional' => sprintf( 177 186 /* translators: 1: The JWT audience. 2: The JWT issuer. */ 178 __( 'When enabled, the plugin will require anonymous users to pass an "Authorization: Bearer <token>" with the token being a valid JSON Web Token (JWT). The plugin will be expecting a JWT with an audience of "%1$s", issuer of "%2$s", and secret that matches the value of the "rest_api_guard_jwt_secret" option. ', 'rest-api-guard' ),187 __( 'When enabled, the plugin will require anonymous users to pass an "Authorization: Bearer <token>" with the token being a valid JSON Web Token (JWT). The plugin will be expecting a JWT with an audience of "%1$s", issuer of "%2$s", and secret that matches the value of the "rest_api_guard_jwt_secret" option.', 'rest-api-guard' ), 179 188 get_jwt_audience(), 180 189 get_jwt_issuer(), … … 185 194 ], 186 195 ); 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 187 216 } 188 217 } … … 206 235 'anonymous_requests_allowlist' => ! empty( $input['anonymous_requests_allowlist'] ) ? sanitize_textarea_field( $input['anonymous_requests_allowlist'] ) : '', 207 236 'anonymous_requests_denylist' => ! empty( $input['anonymous_requests_denylist'] ) ? sanitize_textarea_field( $input['anonymous_requests_denylist'] ) : '', 237 238 208 239 ]; 209 240 } -
rest-api-guard/trunk/vendor/autoload.php
r3021965 r3040295 23 23 require_once __DIR__ . '/composer/autoload_real.php'; 24 24 25 return ComposerAutoloaderInit b3808e08bff289327297fb981027fb31::getLoader();25 return ComposerAutoloaderInit::getLoader(); -
rest-api-guard/trunk/vendor/composer/autoload_real.php
r3021965 r3040295 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit b3808e08bff289327297fb981027fb315 class ComposerAutoloaderInit 6 6 { 7 7 private static $loader; … … 25 25 require __DIR__ . '/platform_check.php'; 26 26 27 spl_autoload_register(array('ComposerAutoloaderInit b3808e08bff289327297fb981027fb31', 'loadClassLoader'), true, true);27 spl_autoload_register(array('ComposerAutoloaderInit', 'loadClassLoader'), true, true); 28 28 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); 29 spl_autoload_unregister(array('ComposerAutoloaderInit b3808e08bff289327297fb981027fb31', 'loadClassLoader'));29 spl_autoload_unregister(array('ComposerAutoloaderInit', 'loadClassLoader')); 30 30 31 31 require __DIR__ . '/autoload_static.php'; 32 call_user_func(\Composer\Autoload\ComposerStaticInit b3808e08bff289327297fb981027fb31::getInitializer($loader));32 call_user_func(\Composer\Autoload\ComposerStaticInit::getInitializer($loader)); 33 33 34 34 $loader->register(true); -
rest-api-guard/trunk/vendor/composer/autoload_static.php
r3021965 r3040295 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit b3808e08bff289327297fb981027fb317 class ComposerStaticInit 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 28 28 { 29 29 return \Closure::bind(function () use ($loader) { 30 $loader->prefixLengthsPsr4 = ComposerStaticInit b3808e08bff289327297fb981027fb31::$prefixLengthsPsr4;31 $loader->prefixDirsPsr4 = ComposerStaticInit b3808e08bff289327297fb981027fb31::$prefixDirsPsr4;32 $loader->classMap = ComposerStaticInit b3808e08bff289327297fb981027fb31::$classMap;30 $loader->prefixLengthsPsr4 = ComposerStaticInit::$prefixLengthsPsr4; 31 $loader->prefixDirsPsr4 = ComposerStaticInit::$prefixDirsPsr4; 32 $loader->classMap = ComposerStaticInit::$classMap; 33 33 34 34 }, null, ClassLoader::class); -
rest-api-guard/trunk/vendor/composer/installed.php
r3021965 r3040295 4 4 'pretty_version' => '1.0.0+no-version-set', 5 5 'version' => '1.0.0.0', 6 'reference' => NULL,6 'reference' => , 7 7 'type' => 'wordpress-plugin', 8 8 'install_path' => __DIR__ . '/../../', … … 14 14 'pretty_version' => '1.0.0+no-version-set', 15 15 'version' => '1.0.0.0', 16 'reference' => NULL,16 'reference' => , 17 17 'type' => 'wordpress-plugin', 18 18 'install_path' => __DIR__ . '/../../',
Note: See TracChangeset
for help on using the changeset viewer.