Plugin Directory

Changeset 3040295

Timestamp:
02/23/2024 02:39:13 PM (5 months ago)
Author:
sean212
Message:

Update to version 1.2.0 from GitHub

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
    12.DS_Store
    23Thumbs.db
     
    2122Makefile
    2223.github
     24
  • rest-api-guard/tags/1.2.0/.gitignore

    r3021965 r3040295  
     1
    12.DS_Store
    23Thumbs.db
  • rest-api-guard/tags/1.2.0/CHANGELOG.md

    r3021965 r3040295  
    22
    33All notable changes to `wp-rest-guard` will be documented in this file.
     4
     5
     6
     7
     8
     9
    410
    511## v1.1.1 - 2024-01-15
  • rest-api-guard/tags/1.2.0/README.md

    r3021965 r3040295  
    11# REST API Guard
    22
    3 Stable tag: 1.1.2
     3Stable tag: 1.
    44
    55Requires at least: 6.0
     
    5757### Preventing Access to Index (`/`) or Namespace Endpoints (`wp/v2`)
    5858
    59 To prevent anonymous users from browing 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:
     59To 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:
    6060
    6161```php
     
    119119```
    120120
    121 ### Require JSON Web Token (JWT) Authentication
     121### Require JSON Web Token (JWT) Authentication
    122122
    123123Anonymous 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 via
    125 code:
     124access the REST API.
     125code:
    126126
    127127```php
     
    135135
    136136```php
    137 add_filter(
    138     'rest_api_guard_jwt_audience',
    139     function ( string $audience ): string {
    140         return 'custom-audience';
    141     }
    142 );
     137add_filter( 'rest_api_guard_jwt_audience', fn ( string $audience ) => 'custom-audience' );
    143138
    144 add_filter(
    145     'rest_api_guard_jwt_issuer',
    146     function ( string $issuer ): string {
    147         return 'https://example.com';
    148     }
    149 );
     139add_filter( 'rest_api_guard_jwt_issuer', fn ( string $issuer ) => 'https://example.com' );
    150140```
    151141
     
    154144
    155145```php
    156 add_filter(
    157     'rest_api_guard_jwt_secret',
    158     function ( string $secret ): string {
    159         return 'my-custom-secret';
    160     }
     146add_filter( 'rest_api_guard_jwt_secret', fn ( string $secret ) => 'my-custom-secret' );
     147```
     148
     149### Allow JWT Authentication for Authenticated Users
     150
     151Authenticated users can be authenticated with the REST API via a JSON Web Token.
     152Similar to the anonymous JWT authentication, users should pass an
     153`Authorization: Bearer <token>` header with their request. This can be
     154configured in the plugin's settings or via code:
     155
     156```php
     157add_filter( 'rest_api_guard_user_authentication_jwt', fn () => true );
     158```
     159
     160### Generating JWTs for Anonymous and Authenticated Users
     161
     162JWTs can be generated by calling the `wp rest-api-guard generate-jwt [--user=<user_id>]`
     163command 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.
    161169);
    162170```
    163 
    164 You can generate a JWT for use with the REST API by calling the
    165 `wp rest-api-guard generate-jwt` command.
    166171
    167172## Testing
  • rest-api-guard/tags/1.2.0/cli.php

    r3021965 r3040295  
    1010WP_CLI::add_command(
    1111    '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;
    1420    },
    1521    [
    1622        'shortdesc' => __( 'Generate a JSON Web Token (JWT).', 'rest-api-guard' ),
    17     ]
     23        'synopsis'  => '[--expiration=<expiration>] [--user=<user>]',
     24    ],
    1825);
  • rest-api-guard/tags/1.2.0/composer.json

    r3021965 r3040295  
    2424        "alleyinteractive/alley-coding-standards": "^2.0",
    2525        "alleyinteractive/composer-wordpress-autoloader": "^1.0",
    26         "mantle-framework/testkit": "^0.7",
    27         "nunomaduro/collision": "^5.0"
     26        "mantle-framework/testkit": "^1.0"
    2827    },
    2928    "config": {
     
    3231            "dealerdirect/phpcodesniffer-composer-installer": true,
    3332            "pestphp/pest-plugin": true
    34         },
    35         "platform": {
    36             "php": "8.0"
    3733        },
    3834        "sort-packages": true
  • rest-api-guard/tags/1.2.0/plugin.php

    r3021965 r3040295  
    44 * Plugin URI: https://github.com/alleyinteractive/wp-rest-api-guard
    55 * Description: Restrict and control access to the REST API
    6  * Version: 1.1.2
     6 * Version: 1.
    77 * Author: Sean Fisher
    88 * Author URI: https://alley.co/
     
    2424use WP_REST_Request;
    2525use WP_REST_Server;
     26
    2627
    2728if ( ! defined( 'ABSPATH' ) ) {
     
    5960    }
    6061
    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            }
    110134        }
    111135    }
     
    284308    // Generate the JWT secret if it does not exist.
    285309    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 ) );
    287311    }
    288312
     
    298322 * Generate a JSON Web Token (JWT).
    299323 *
     324
     325
     326
     327
    300328 * @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 */
     332function 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' );
    312355}
    313356
  • rest-api-guard/tags/1.2.0/readme.txt

    r3021965 r3040295  
    11=== REST API Guard ===
    2 Stable tag: 1.1.2
     2Stable tag: 1.
    33Requires at least: 6.0
    44Tested up to: 6.3
     
    7272### Restrict Anonymous Access to Specific Namespaces/Routes (Denylist)
    7373
    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:
     74Anonymous users can be restricted from specific namespaces/routes. This acts as
     75a denylist for specific paths that an anonymous user cannot access. The paths
     76support regular expressions for matching. The use of the allowlist takes
     77priority over this denylist. This can be configured in the plugin's settings or
     78via code:
    7579
    7680    add_filter(
     
    8993
    9094Anonymous 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 via
    92 code:
     95access the REST API.
     96code:
    9397
    94 ```php
    95 add_filter( 'rest_api_guard_authentication_jwt', fn () => true );
    96 ```
     98    add_filter( 'rest_api_guard_authentication_jwt', fn () => true );
    9799
    98100Out 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
     103plugin's settings or via code:
    100104
    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    );
    108111
    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    );
    116118
    117119The JWT's secret will be autogenerated and stored in the database in the
    118120`rest_api_guard_jwt_secret` option. The secret can also be changed via code:
    119121
    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    );
    128128
    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
     131Authenticated users can be authenticated with the REST API via a JSON Web Token.
     132Similar to the anonymous JWT authentication, users should pass an
     133`Authorization: Bearer <token>` header with their request. This can be
     134configured 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
     140JWTs 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  
    2828 */
    2929function on_admin_menu() {
     30
     31
     32
     33
     34
     35
     36
     37
     38
    3039    add_options_page(
    3140        __( 'REST API Guard', 'rest-api-guard' ),
     
    176185                'additional'  => sprintf(
    177186                    /* 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' ),
    179188                    get_jwt_audience(),
    180189                    get_jwt_issuer(),
     
    185194            ],
    186195        );
     196
     197
     198
     199
     200
     201
     202
     203
     204
     205
     206
     207
     208
     209
     210
     211
     212
     213
     214
     215
    187216    }
    188217}
     
    206235        'anonymous_requests_allowlist' => ! empty( $input['anonymous_requests_allowlist'] ) ? sanitize_textarea_field( $input['anonymous_requests_allowlist'] ) : '',
    207236        'anonymous_requests_denylist'  => ! empty( $input['anonymous_requests_denylist'] ) ? sanitize_textarea_field( $input['anonymous_requests_denylist'] ) : '',
     237
     238
    208239    ];
    209240}
  • rest-api-guard/tags/1.2.0/vendor/autoload.php

    r3021965 r3040295  
    2323require_once __DIR__ . '/composer/autoload_real.php';
    2424
    25 return ComposerAutoloaderInitb3808e08bff289327297fb981027fb31::getLoader();
     25return ComposerAutoloaderInit::getLoader();
  • rest-api-guard/tags/1.2.0/vendor/composer/autoload_real.php

    r3021965 r3040295  
    33// autoload_real.php @generated by Composer
    44
    5 class ComposerAutoloaderInitb3808e08bff289327297fb981027fb31
     5class ComposerAutoloaderInit
    66{
    77    private static $loader;
     
    2525        require __DIR__ . '/platform_check.php';
    2626
    27         spl_autoload_register(array('ComposerAutoloaderInitb3808e08bff289327297fb981027fb31', 'loadClassLoader'), true, true);
     27        spl_autoload_register(array('ComposerAutoloaderInit', 'loadClassLoader'), true, true);
    2828        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
    29         spl_autoload_unregister(array('ComposerAutoloaderInitb3808e08bff289327297fb981027fb31', 'loadClassLoader'));
     29        spl_autoload_unregister(array('ComposerAutoloaderInit', 'loadClassLoader'));
    3030
    3131        require __DIR__ . '/autoload_static.php';
    32         call_user_func(\Composer\Autoload\ComposerStaticInitb3808e08bff289327297fb981027fb31::getInitializer($loader));
     32        call_user_func(\Composer\Autoload\ComposerStaticInit::getInitializer($loader));
    3333
    3434        $loader->register(true);
  • rest-api-guard/tags/1.2.0/vendor/composer/autoload_static.php

    r3021965 r3040295  
    55namespace Composer\Autoload;
    66
    7 class ComposerStaticInitb3808e08bff289327297fb981027fb31
     7class ComposerStaticInit
    88{
    99    public static $prefixLengthsPsr4 = array (
     
    2828    {
    2929        return \Closure::bind(function () use ($loader) {
    30             $loader->prefixLengthsPsr4 = ComposerStaticInitb3808e08bff289327297fb981027fb31::$prefixLengthsPsr4;
    31             $loader->prefixDirsPsr4 = ComposerStaticInitb3808e08bff289327297fb981027fb31::$prefixDirsPsr4;
    32             $loader->classMap = ComposerStaticInitb3808e08bff289327297fb981027fb31::$classMap;
     30            $loader->prefixLengthsPsr4 = ComposerStaticInit::$prefixLengthsPsr4;
     31            $loader->prefixDirsPsr4 = ComposerStaticInit::$prefixDirsPsr4;
     32            $loader->classMap = ComposerStaticInit::$classMap;
    3333
    3434        }, null, ClassLoader::class);
  • rest-api-guard/tags/1.2.0/vendor/composer/installed.php

    r3021965 r3040295  
    44        'pretty_version' => '1.0.0+no-version-set',
    55        'version' => '1.0.0.0',
    6         'reference' => NULL,
     6        'reference' => ,
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => '1.0.0+no-version-set',
    1515            'version' => '1.0.0.0',
    16             'reference' => NULL,
     16            'reference' => ,
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
  • rest-api-guard/trunk/.distignore

    r3021965 r3040295  
     1
    12.DS_Store
    23Thumbs.db
     
    2122Makefile
    2223.github
     24
  • rest-api-guard/trunk/.gitignore

    r3021965 r3040295  
     1
    12.DS_Store
    23Thumbs.db
  • rest-api-guard/trunk/CHANGELOG.md

    r3021965 r3040295  
    22
    33All notable changes to `wp-rest-guard` will be documented in this file.
     4
     5
     6
     7
     8
     9
    410
    511## v1.1.1 - 2024-01-15
  • rest-api-guard/trunk/README.md

    r3021965 r3040295  
    11# REST API Guard
    22
    3 Stable tag: 1.1.2
     3Stable tag: 1.
    44
    55Requires at least: 6.0
     
    5757### Preventing Access to Index (`/`) or Namespace Endpoints (`wp/v2`)
    5858
    59 To prevent anonymous users from browing 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:
     59To 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:
    6060
    6161```php
     
    119119```
    120120
    121 ### Require JSON Web Token (JWT) Authentication
     121### Require JSON Web Token (JWT) Authentication
    122122
    123123Anonymous 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 via
    125 code:
     124access the REST API.
     125code:
    126126
    127127```php
     
    135135
    136136```php
    137 add_filter(
    138     'rest_api_guard_jwt_audience',
    139     function ( string $audience ): string {
    140         return 'custom-audience';
    141     }
    142 );
     137add_filter( 'rest_api_guard_jwt_audience', fn ( string $audience ) => 'custom-audience' );
    143138
    144 add_filter(
    145     'rest_api_guard_jwt_issuer',
    146     function ( string $issuer ): string {
    147         return 'https://example.com';
    148     }
    149 );
     139add_filter( 'rest_api_guard_jwt_issuer', fn ( string $issuer ) => 'https://example.com' );
    150140```
    151141
     
    154144
    155145```php
    156 add_filter(
    157     'rest_api_guard_jwt_secret',
    158     function ( string $secret ): string {
    159         return 'my-custom-secret';
    160     }
     146add_filter( 'rest_api_guard_jwt_secret', fn ( string $secret ) => 'my-custom-secret' );
     147```
     148
     149### Allow JWT Authentication for Authenticated Users
     150
     151Authenticated users can be authenticated with the REST API via a JSON Web Token.
     152Similar to the anonymous JWT authentication, users should pass an
     153`Authorization: Bearer <token>` header with their request. This can be
     154configured in the plugin's settings or via code:
     155
     156```php
     157add_filter( 'rest_api_guard_user_authentication_jwt', fn () => true );
     158```
     159
     160### Generating JWTs for Anonymous and Authenticated Users
     161
     162JWTs can be generated by calling the `wp rest-api-guard generate-jwt [--user=<user_id>]`
     163command 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.
    161169);
    162170```
    163 
    164 You can generate a JWT for use with the REST API by calling the
    165 `wp rest-api-guard generate-jwt` command.
    166171
    167172## Testing
  • rest-api-guard/trunk/cli.php

    r3021965 r3040295  
    1010WP_CLI::add_command(
    1111    '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;
    1420    },
    1521    [
    1622        'shortdesc' => __( 'Generate a JSON Web Token (JWT).', 'rest-api-guard' ),
    17     ]
     23        'synopsis'  => '[--expiration=<expiration>] [--user=<user>]',
     24    ],
    1825);
  • rest-api-guard/trunk/composer.json

    r3021965 r3040295  
    2424        "alleyinteractive/alley-coding-standards": "^2.0",
    2525        "alleyinteractive/composer-wordpress-autoloader": "^1.0",
    26         "mantle-framework/testkit": "^0.7",
    27         "nunomaduro/collision": "^5.0"
     26        "mantle-framework/testkit": "^1.0"
    2827    },
    2928    "config": {
     
    3231            "dealerdirect/phpcodesniffer-composer-installer": true,
    3332            "pestphp/pest-plugin": true
    34         },
    35         "platform": {
    36             "php": "8.0"
    3733        },
    3834        "sort-packages": true
  • rest-api-guard/trunk/plugin.php

    r3021965 r3040295  
    44 * Plugin URI: https://github.com/alleyinteractive/wp-rest-api-guard
    55 * Description: Restrict and control access to the REST API
    6  * Version: 1.1.2
     6 * Version: 1.
    77 * Author: Sean Fisher
    88 * Author URI: https://alley.co/
     
    2424use WP_REST_Request;
    2525use WP_REST_Server;
     26
    2627
    2728if ( ! defined( 'ABSPATH' ) ) {
     
    5960    }
    6061
    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            }
    110134        }
    111135    }
     
    284308    // Generate the JWT secret if it does not exist.
    285309    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 ) );
    287311    }
    288312
     
    298322 * Generate a JSON Web Token (JWT).
    299323 *
     324
     325
     326
     327
    300328 * @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 */
     332function 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' );
    312355}
    313356
  • rest-api-guard/trunk/readme.txt

    r3021965 r3040295  
    11=== REST API Guard ===
    2 Stable tag: 1.1.2
     2Stable tag: 1.
    33Requires at least: 6.0
    44Tested up to: 6.3
     
    7272### Restrict Anonymous Access to Specific Namespaces/Routes (Denylist)
    7373
    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:
     74Anonymous users can be restricted from specific namespaces/routes. This acts as
     75a denylist for specific paths that an anonymous user cannot access. The paths
     76support regular expressions for matching. The use of the allowlist takes
     77priority over this denylist. This can be configured in the plugin's settings or
     78via code:
    7579
    7680    add_filter(
     
    8993
    9094Anonymous 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 via
    92 code:
     95access the REST API.
     96code:
    9397
    94 ```php
    95 add_filter( 'rest_api_guard_authentication_jwt', fn () => true );
    96 ```
     98    add_filter( 'rest_api_guard_authentication_jwt', fn () => true );
    9799
    98100Out 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
     103plugin's settings or via code:
    100104
    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    );
    108111
    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    );
    116118
    117119The JWT's secret will be autogenerated and stored in the database in the
    118120`rest_api_guard_jwt_secret` option. The secret can also be changed via code:
    119121
    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    );
    128128
    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
     131Authenticated users can be authenticated with the REST API via a JSON Web Token.
     132Similar to the anonymous JWT authentication, users should pass an
     133`Authorization: Bearer <token>` header with their request. This can be
     134configured 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
     140JWTs 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  
    2828 */
    2929function on_admin_menu() {
     30
     31
     32
     33
     34
     35
     36
     37
     38
    3039    add_options_page(
    3140        __( 'REST API Guard', 'rest-api-guard' ),
     
    176185                'additional'  => sprintf(
    177186                    /* 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' ),
    179188                    get_jwt_audience(),
    180189                    get_jwt_issuer(),
     
    185194            ],
    186195        );
     196
     197
     198
     199
     200
     201
     202
     203
     204
     205
     206
     207
     208
     209
     210
     211
     212
     213
     214
     215
    187216    }
    188217}
     
    206235        'anonymous_requests_allowlist' => ! empty( $input['anonymous_requests_allowlist'] ) ? sanitize_textarea_field( $input['anonymous_requests_allowlist'] ) : '',
    207236        'anonymous_requests_denylist'  => ! empty( $input['anonymous_requests_denylist'] ) ? sanitize_textarea_field( $input['anonymous_requests_denylist'] ) : '',
     237
     238
    208239    ];
    209240}
  • rest-api-guard/trunk/vendor/autoload.php

    r3021965 r3040295  
    2323require_once __DIR__ . '/composer/autoload_real.php';
    2424
    25 return ComposerAutoloaderInitb3808e08bff289327297fb981027fb31::getLoader();
     25return ComposerAutoloaderInit::getLoader();
  • rest-api-guard/trunk/vendor/composer/autoload_real.php

    r3021965 r3040295  
    33// autoload_real.php @generated by Composer
    44
    5 class ComposerAutoloaderInitb3808e08bff289327297fb981027fb31
     5class ComposerAutoloaderInit
    66{
    77    private static $loader;
     
    2525        require __DIR__ . '/platform_check.php';
    2626
    27         spl_autoload_register(array('ComposerAutoloaderInitb3808e08bff289327297fb981027fb31', 'loadClassLoader'), true, true);
     27        spl_autoload_register(array('ComposerAutoloaderInit', 'loadClassLoader'), true, true);
    2828        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
    29         spl_autoload_unregister(array('ComposerAutoloaderInitb3808e08bff289327297fb981027fb31', 'loadClassLoader'));
     29        spl_autoload_unregister(array('ComposerAutoloaderInit', 'loadClassLoader'));
    3030
    3131        require __DIR__ . '/autoload_static.php';
    32         call_user_func(\Composer\Autoload\ComposerStaticInitb3808e08bff289327297fb981027fb31::getInitializer($loader));
     32        call_user_func(\Composer\Autoload\ComposerStaticInit::getInitializer($loader));
    3333
    3434        $loader->register(true);
  • rest-api-guard/trunk/vendor/composer/autoload_static.php

    r3021965 r3040295  
    55namespace Composer\Autoload;
    66
    7 class ComposerStaticInitb3808e08bff289327297fb981027fb31
     7class ComposerStaticInit
    88{
    99    public static $prefixLengthsPsr4 = array (
     
    2828    {
    2929        return \Closure::bind(function () use ($loader) {
    30             $loader->prefixLengthsPsr4 = ComposerStaticInitb3808e08bff289327297fb981027fb31::$prefixLengthsPsr4;
    31             $loader->prefixDirsPsr4 = ComposerStaticInitb3808e08bff289327297fb981027fb31::$prefixDirsPsr4;
    32             $loader->classMap = ComposerStaticInitb3808e08bff289327297fb981027fb31::$classMap;
     30            $loader->prefixLengthsPsr4 = ComposerStaticInit::$prefixLengthsPsr4;
     31            $loader->prefixDirsPsr4 = ComposerStaticInit::$prefixDirsPsr4;
     32            $loader->classMap = ComposerStaticInit::$classMap;
    3333
    3434        }, null, ClassLoader::class);
  • rest-api-guard/trunk/vendor/composer/installed.php

    r3021965 r3040295  
    44        'pretty_version' => '1.0.0+no-version-set',
    55        'version' => '1.0.0.0',
    6         'reference' => NULL,
     6        'reference' => ,
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => '1.0.0+no-version-set',
    1515            'version' => '1.0.0.0',
    16             'reference' => NULL,
     16            'reference' => ,
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.