diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 94fe82a2..6e7a101f 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -21,7 +21,7 @@ jobs: core: - {name: 'WP latest', version: 'latest'} - {name: 'WP trunk', version: 'WordPress/WordPress#master'} - - {name: 'WP minimum', version: 'WordPress/WordPress#4.6'} + - {name: 'WP minimum', version: 'WordPress/WordPress#5.7'} steps: - name: Checkout diff --git a/.github/workflows/php-compatibility.yml b/.github/workflows/php-compatibility.yml index 30d691ef..a2a673f9 100644 --- a/.github/workflows/php-compatibility.yml +++ b/.github/workflows/php-compatibility.yml @@ -11,7 +11,7 @@ on: jobs: php-compatibility: - name: PHP minimum 5.6 + name: PHP minimum 7.4 runs-on: ubuntu-latest @@ -22,7 +22,7 @@ jobs: - name: Set PHP version uses: shivammathur/setup-php@v2 with: - php-version: '7.3' + php-version: '7.4' tools: composer:v2 coverage: none @@ -30,4 +30,4 @@ jobs: run: composer install - name: Run PHP Compatibility - run: vendor/bin/phpcs . --standard=PHPCompatibilityWP --ignore=vendor --extensions=php --runtime-set testVersion 5.6- \ No newline at end of file + run: vendor/bin/phpcs . --standard=PHPCompatibilityWP --ignore=vendor --extensions=php --runtime-set testVersion 7.4- \ No newline at end of file diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 8ba469fb..4e1dae48 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -24,7 +24,7 @@ jobs: - name: Set PHP version uses: shivammathur/setup-php@v2 with: - php-version: '7.3' + php-version: '7.4' coverage: none tools: composer:v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e6496ca6..75e5bcf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,20 @@ All notable changes to this project will be documented in this file, per [the Keep a Changelog standard](http://keepachangelog.com/). Moving forward, this project will (more strictly) adhere to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [Unreleased] - TBD + +## [7.3.2] - 2022-08-29 + +### Added +- New filter - `rsa_get_client_ip_address_filter_flags` to modify the range of accepted IP addresses (props [@dsXLII](https://github.com/dsXLII), [@dinhtungdu](https://github.com/dinhtungdu), [@Sidsector9](https://github.com/Sidsector9) via [#113](https://github.com/10up/restricted-site-access/pull/113)). + +### Changed +- Avoid disjointed plugin settings (props [@helen](https://github.com/helen), [@peterwilsoncc](https://github.com/peterwilsoncc), [@Sidsector9](https://github.com/Sidsector9) via [#200](https://github.com/10up/restricted-site-access/pull/200)). +- Bump minimum WordPress version from 5.0 to 5.7 (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@faisal-alvi](https://github.com/faisal-alvi) via [#207](https://github.com/10up/restricted-site-access/pull/207)). +- Bump minimum PHP version from 5.6 to 7.4 (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@faisal-alvi](https://github.com/faisal-alvi) via [#207](https://github.com/10up/restricted-site-access/pull/207)). + +### Security +- New filters - `rsa_trusted_proxies` and `rsa_trusted_headers` have been added to help prevent IP spoofing attacks (props [@dkotter](https://github.com/dkotter), [@peterwilsoncc](https://github.com/peterwilsoncc), [@marcS0H](https://github.com/marcS0H), [@DanielRuf](https://github.com/DanielRuf), [@Sidsector9](https://github.com/Sidsector9) via [#198](https://github.com/10up/restricted-site-access/pull/198)). ## [7.3.1] - 2022-06-30 ### Added @@ -230,6 +243,7 @@ All notable changes to this project will be documented in this file, per [the Ke - Initial public release [Unreleased]: https://github.com/10up/restricted-site-access/compare/trunk...develop +[7.3.2]: https://github.com/10up/restricted-site-access/compare/7.3.1...7.3.2 [7.3.1]: https://github.com/10up/restricted-site-access/compare/7.3.0...7.3.1 [7.3.0]: https://github.com/10up/restricted-site-access/compare/7.2.0...7.3.0 [7.2.0]: https://github.com/10up/restricted-site-access/compare/7.1.0...7.2.0 diff --git a/CREDITS.md b/CREDITS.md index 341db32a..863b8c7e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -10,7 +10,7 @@ The following individuals are responsible for curating the list of issues, respo Thank you to all the people who have already contributed to this repository via bug reports, code, design, ideas, project management, translation, testing, etc. -[Jake Goldman (@jakemgold)](https://github.com/jakemgold), [Joey Blake (@joeyblake)](https://github.com/joeyblake), [Steve Grunwell (@stevegrunwell)](https://github.com/stevegrunwell), [Grant Mangham (@vancoder)](https://github.com/vancoder), [@jmata-loop](https://github.com/jmata-loop), [Taylor Lovett (@tlovett1)](https://github.com/tlovett1), [Ivan Kristianto (@ivankristianto)](https://github.com/ivankristianto), [Mika Epstein (@Ipstenu)](https://github.com/Ipstenu), [Adam Silverstein (@adamsilverstein)](https://github.com/adamsilverstein), [Prasath Nadarajah (@nprasath002)](https://github.com/nprasath002), [Mathieu Viet (@imath)](https://github.com/imath), [Ryan Welcher (@ryanwelcher)](https://github.com/ryanwelcher), [Peter Tasker (@ptasker)](https://github.com/ptasker), [Darin Kotter (@dkotter)](https://github.com/dkotter), [Helen Hou-Sandí (@helen)](https://github.com/helen), [Echo (@ChaosExAnima)](https://github.com/ChaosExAnima), [William Patton (@pattonwebz)](https://github.com/pattonwebz), [Oscar Sanchez S. (@oscarssanchez)](https://github.com/oscarssanchez), [Pete Nelson (@petenelson)](https://github.com/petenelson), [Nate Allen (@nate-allen)](https://github.com/nate-allen), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Evan Mattson (@aaemnnosttv)](https://github.com/aaemnnosttv), [@JayWood](https://github.com/JayWood), [Ivan Kruchkoff (@ivankruchkoff)](https://github.com/ivankruchkoff), [Paul Schreiber (@paulschreiber)](https://github.com/paulschreiber), [Nick Lobeck (@eightam)](https://github.com/eightam), [Tung Du (@dinhtungdu)](https://github.com/dinhtungdu), [Siddharth Thevaril (@Sidsector9)](https://github.com/Sidsector9), [Mikel King (@mikelking)](https://github.com/mikelking), [Max Lyuchin (@cadic)](https://github.com/cadic), [Crisoforo Gaspar Hernández (@mitogh)](https://github.com/mitogh), [Ankit K Gupta (@ankitguptaindia)](https://github.com/ankitguptaindia), [Brandon Berg (@BBerg10up)](https://github.com/BBerg10up), [Justin Kopepasah (@kopepasah)](https://github.com/kopepasah), [Faisal Alvi (@faisal-alvi)](https://github.com/faisal-alvi), [Wayne K. Walrath (@wkw)](https://github.com/wkw), [Ivan Lopez (@ivanlopez)](https://github.com/ivanlopez), [Chuck Scott (@n8dnx)](https://github.com/n8dnx), [Leho Kraav (@lkraav)](https://github.com/lkraav), [Pablo Amato (@pabamato)](https://github.com/pabamato), [Pedro Mendonça (@pedro-mendonca)](https://github.com/pedro-mendonca), [Sudip Dadhaniya (@sudip-10up)](https://github.com/sudip-10up), [Stephanie Walters (@PypWalters)](https://github.com/PypWalters), [Peter Wilson (@peterwilsoncc)](https://github.com/peterwilsoncc), [Dharmesh Patel (@iamdharmesh)](https://github.com/iamdharmesh). +[Jake Goldman (@jakemgold)](https://github.com/jakemgold), [Joey Blake (@joeyblake)](https://github.com/joeyblake), [Steve Grunwell (@stevegrunwell)](https://github.com/stevegrunwell), [Grant Mangham (@vancoder)](https://github.com/vancoder), [@jmata-loop](https://github.com/jmata-loop), [Taylor Lovett (@tlovett1)](https://github.com/tlovett1), [Ivan Kristianto (@ivankristianto)](https://github.com/ivankristianto), [Mika Epstein (@Ipstenu)](https://github.com/Ipstenu), [Adam Silverstein (@adamsilverstein)](https://github.com/adamsilverstein), [Prasath Nadarajah (@nprasath002)](https://github.com/nprasath002), [Mathieu Viet (@imath)](https://github.com/imath), [Ryan Welcher (@ryanwelcher)](https://github.com/ryanwelcher), [Peter Tasker (@ptasker)](https://github.com/ptasker), [Darin Kotter (@dkotter)](https://github.com/dkotter), [Helen Hou-Sandí (@helen)](https://github.com/helen), [Echo (@ChaosExAnima)](https://github.com/ChaosExAnima), [William Patton (@pattonwebz)](https://github.com/pattonwebz), [Oscar Sanchez S. (@oscarssanchez)](https://github.com/oscarssanchez), [Pete Nelson (@petenelson)](https://github.com/petenelson), [Nate Allen (@nate-allen)](https://github.com/nate-allen), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Evan Mattson (@aaemnnosttv)](https://github.com/aaemnnosttv), [@JayWood](https://github.com/JayWood), [Ivan Kruchkoff (@ivankruchkoff)](https://github.com/ivankruchkoff), [Paul Schreiber (@paulschreiber)](https://github.com/paulschreiber), [Nick Lobeck (@eightam)](https://github.com/eightam), [Tung Du (@dinhtungdu)](https://github.com/dinhtungdu), [Siddharth Thevaril (@Sidsector9)](https://github.com/Sidsector9), [Mikel King (@mikelking)](https://github.com/mikelking), [Max Lyuchin (@cadic)](https://github.com/cadic), [Crisoforo Gaspar Hernández (@mitogh)](https://github.com/mitogh), [Ankit K Gupta (@ankitguptaindia)](https://github.com/ankitguptaindia), [Brandon Berg (@BBerg10up)](https://github.com/BBerg10up), [Justin Kopepasah (@kopepasah)](https://github.com/kopepasah), [Faisal Alvi (@faisal-alvi)](https://github.com/faisal-alvi), [Wayne K. Walrath (@wkw)](https://github.com/wkw), [Ivan Lopez (@ivanlopez)](https://github.com/ivanlopez), [Chuck Scott (@n8dnx)](https://github.com/n8dnx), [Leho Kraav (@lkraav)](https://github.com/lkraav), [Pablo Amato (@pabamato)](https://github.com/pabamato), [Pedro Mendonça (@pedro-mendonca)](https://github.com/pedro-mendonca), [Sudip Dadhaniya (@sudip-10up)](https://github.com/sudip-10up), [Stephanie Walters (@PypWalters)](https://github.com/PypWalters), [Peter Wilson (@peterwilsoncc)](https://github.com/peterwilsoncc), [Dharmesh Patel (@iamdharmesh)](https://github.com/iamdharmesh), [Vikram Moparthy (@vikrampm1)](https://github.com/vikrampm1), [Marc-Alexandre Montpas (@marcS0H)](https://github.com/marcS0H), [Daniel Ruf (@DanielRuf)](https://github.com/DanielRuf), [David E. Smith (@dsXLII)](https://github.com/dsXLII). ## Libraries diff --git a/README.md b/README.md index c2028e69..d99cb7e4 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,51 @@ Visitors that are not logged in or allowed by IP address will not be able to bro Restricted Site Access is not meant to be a top secret data safe, but simply a reliable and convenient way to handle unwanted visitors. +In 7.3.2, two new filters have been added that can be utilized to help prevent IP spoofing attacks. The first filter allows you to set up a list of approved proxy IP addresses and the second allows you to set up a list of approved HTTP headers. By default, these filters will not change existing behavior. It is recommended to review these filters and utilize them appropriately for your site to secure things further. + +If your site is not running behind a proxy, we recommend doing the following: + +```php +add_filter( 'rsa_trusted_headers', '__return_empty_array' ); +``` + +This will then only use the `REMOTE_ADDR` HTTP header to determine the IP address of the visitor. This header can't be spoofed, so this will increase security. + +If your site is running behind a proxy (like a CDN), you can't rely on the `REMOTE_ADDR` HTTP header, as this will contain the IP address of the proxy, not the user. If your proxy uses static IP addresses, we recommend using the `rsa_trusted_proxies` filter to set those trusted IP addresses: + +```php +add_filter( 'rsa_trusted_proxies', 'my_rsa_trusted_proxies' ); + +function my_rsa_trusted_proxies( $trusted_proxies = array() ) { + // Set one or more trusted proxy IP addresses. + $proxy_ips = array( + '10.0.0.0/24', + '10.0.0.0/32', + ); + $trusted_proxies = array_merge( $trusted_proxies, $proxy_ips ); + + return array_unique( $trusted_proxies ); +} +``` + +And then use the `rsa_trusted_headers` filter to set which HTTP headers you want to trust. Consult with your proxy provider to determine which header(s) they use to hold the original client IP: + +```php +add_filter( 'rsa_trusted_headers', 'my_rsa_trusted_headers' ); + +function my_rsa_trusted_headers( $trusted_headers = array() ) { + // Set one or more trusted HTTP headers. + $headers = array( + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED', + ); + + return $headers; +} +``` + +If your proxy does not use static IP addresses, you can still utilize the `rsa_trusted_headers` filter to change which HTTP headers you want to trust. + ### I received a warning about page caching. What does it mean? Page caching plugins often hook into WordPress to quickly serve the last cached output of a page before we can check to see if a visitor’s access should be restricted. Not all page caching plugins behave the same way, but several solutions - including external solutions we might not detect - can cause restricted pages to be publicly served regardless of your settings. diff --git a/package.json b/package.json index 96a5321d..e8f3557e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "restricted-site-access", - "version": "7.3.1", + "version": "7.3.2", "description": "Limit access to visitors who are logged in or allowed by IP addresses. Includes many options for handling blocked visitors.", "homepage": "https://github.com/10up/restricted-site-access#readme", "license": "GPL-2.0-or-later", diff --git a/readme.txt b/readme.txt index 7f43a533..13d175b3 100644 --- a/readme.txt +++ b/readme.txt @@ -2,10 +2,10 @@ Contributors: 10up, jakemgold, rcbth, thinkoomph, tlovett1, jeffpaul, nomnom99 Donate link: https://10up.com/plugins/restricted-site-access-wordpress/ Tags: privacy, restricted, restrict, privacy, limited, permissions, security, block -Requires at least: 5.0 +Requires at least: 5.7 Tested up to: 6.0 -Stable tag: 7.3.1 -Requires PHP: 5.6 +Stable tag: 7.3.2 +Requires PHP: 7.4 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -64,6 +64,51 @@ Visitors that are not logged in or allowed by IP address will not be able to bro Restricted Site Access is not meant to be a top secret data safe, but simply a reliable and convenient way to handle unwanted visitors. +In 7.3.2, two new filters have been added that can be utilized to help prevent IP spoofing attacks. The first filter allows you to set up a list of approved proxy IP addresses and the second allows you to set up a list of approved HTTP headers. By default, these filters will not change existing behavior. It is recommended to review these filters and utilize them appropriately for your site to secure things further. + +If your site is not running behind a proxy, we recommend doing the following: + +` +add_filter( 'rsa_trusted_headers', '__return_empty_array' ); +` + +This will then only use the `REMOTE_ADDR` HTTP header to determine the IP address of the visitor. This header can't be spoofed, so this will increase security. + +If your site is running behind a proxy (like a CDN), you can't rely on the `REMOTE_ADDR` HTTP header, as this will contain the IP address of the proxy, not the user. If your proxy uses static IP addresses, we recommend using the `rsa_trusted_proxies` filter to set those trusted IP addresses: + +` +add_filter( 'rsa_trusted_proxies', 'my_rsa_trusted_proxies' ); + +function my_rsa_trusted_proxies( $trusted_proxies = array() ) { + // Set one or more trusted proxy IP addresses. + $proxy_ips = array( + '10.0.0.0/24', + '10.0.0.0/32', + ); + $trusted_proxies = array_merge( $trusted_proxies, $proxy_ips ); + + return array_unique( $trusted_proxies ); +} +` + +And then use the `rsa_trusted_headers` filter to set which HTTP headers you want to trust. Consult with your proxy provider to determine which header(s) they use to hold the original client IP: + +` +add_filter( 'rsa_trusted_headers', 'my_rsa_trusted_headers' ); + +function my_rsa_trusted_headers( $trusted_headers = array() ) { + // Set one or more trusted HTTP headers. + $headers = array( + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED', + ); + + return $headers; +} +` + +If your proxy does not use static IP addresses, you can still utilize the `rsa_trusted_headers` filter to change which HTTP headers you want to trust. + = I received a warning about page caching. What does it mean? = Page caching plugins often hook into WordPress to quickly serve the last cached output of a page before we can check to see if a visitor’s access should be restricted. Not all page caching plugins behave the same way, but several solutions - including external solutions we might not detect - can cause restricted pages to be publicly served regardless of your settings. @@ -153,6 +198,13 @@ Please note that setting `RSA_FORCE_RESTRICTION` will override `RSA_FORBID_RESTR == Changelog == += 7.3.2 - 2022-08-29 = +* **Added:** New filter - `rsa_get_client_ip_address_filter_flags` to modify the range of accepted IP addresses. +* **Changed:** Avoid disjointed plugin settings (props [@helen](https://github.com/helen), [@peterwilsoncc](https://github.com/peterwilsoncc), [@Sidsector9](https://github.com/Sidsector9)). +* **Changed:** Bump minimum WordPress version from 5.0 to 5.7 (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@faisal-alvi](https://github.com/faisal-alvi)). +* **Changed:** Bump minimum PHP version from 5.6 to 7.4 (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@faisal-alvi](https://github.com/faisal-alvi)). +* **Security:** New filters - `rsa_trusted_proxies` and `rsa_trusted_headers` have been added to help prevent IP spoofing attacks. + = 7.3.1 - 2022-06-30 = * **Added:** PHP8 compatibility check GitHub Action (props [@Sidsector9](https://github.com/Sidsector9), [dkotter](https://github.com/dkotter)). * **Added:** Dependency security scanning GitHub Action (props [@jeffpaul](https://github.com/jeffpaul)). @@ -315,13 +367,9 @@ __Note: There is currently an edge case bug affecting IP whitelisting. This bug == Upgrade Notice == -= 5.1 = -Drops support for versions of WordPress prior to 3.5. - -= 4.0 = -This update improves performance, refines the user interface, and adds support for showing restricted visitors a specific page. Please be advised that this udpate is specifically designed for WordPress 3.2+, and like WordPress 3.2, no longer supports PHP < 5.2.4. - -== Upgrade Notice == += 7.3.2 = +Drops support for versions of WordPress prior to 5.7. +Drops support for versions of PHP prior to 7.4. = 6.2.1 = IMPORTANT MULTISITE FUNCTIONALITY CHANGE: User access is now checked against their role on a given site in multisite. To restore previous behavior, use the new restricted_site_access_user_can_access filter. @@ -331,3 +379,9 @@ IMPORTANT MULTISITE FUNCTIONALITY CHANGE: User access is now checked against the = 6.1.0 = * Important: version 6.1 improves testing visitors for allowed IP addresses ("Unrestricted IP addresses"). We recommend testing IP based restrictions after updating. + += 5.1 = +Drops support for versions of WordPress prior to 3.5. + += 4.0 = +This update improves performance, refines the user interface, and adds support for showing restricted visitors a specific page. Please be advised that this udpate is specifically designed for WordPress 3.2+, and like WordPress 3.2, no longer supports PHP < 5.2.4. diff --git a/restricted_site_access.php b/restricted_site_access.php index d40af720..84ed7460 100644 --- a/restricted_site_access.php +++ b/restricted_site_access.php @@ -3,9 +3,9 @@ * Plugin Name: Restricted Site Access * Plugin URI: https://10up.com/plugins/restricted-site-access-wordpress/ * Description: Limit access your site to visitors who are logged in or accessing the site from a set of specific IP addresses. Send restricted visitors to the log in page, redirect them, or display a message or page. Powerful control over redirection, including SEO friendly redirect headers. Great solution for Extranets, publicly hosted Intranets, or parallel development sites. - * Version: 7.3.1 - * Requires at least: 5.0 - * Requires PHP: 5.6 + * Version: 7.3.2 + * Requires at least: 5.7 + * Requires PHP: 7.4 * Author: Jake Goldman, 10up, Oomph * Author URI: https://10up.com * License: GPL v2 or later @@ -13,7 +13,7 @@ * Text Domain: restricted-site-access */ -define( 'RSA_VERSION', '7.3.1' ); +define( 'RSA_VERSION', '7.3.2' ); /** * Class responsible for all plugin funcitonality. @@ -372,7 +372,7 @@ public static function restrict_access_check( $wp ) { // iterate through the allow list. foreach ( $allowed_ips as $line ) { - if ( self::ip_in_range( $remote_ip, $line ) ) { + if ( $remote_ip && self::ip_in_range( $remote_ip, $line ) ) { /** * Fires when an ip address match occurs. @@ -1529,8 +1529,51 @@ public static function ip_in_range( $ip, $range ) { * @return string */ public static function get_client_ip_address() { - $ip = ''; - $headers = array( + // REMOTE_ADDR IP address. + $remote_addr_header_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : false; + + // Return if REMOTE_ADDR is not set. + if ( empty( $remote_addr_header_ip ) ) { + return ''; + } + + /** + * Filter hook to set array of trusted proxies. + * + * Some reverse proxies (like AWS Elastic Load Balancing) don't have + * a static IP address or even a range that you can target with the CIDR notation. + * In this case, you'll need to - very carefully - trust all proxies by setting + * $trusted_proxies to an empty array - (default behaviour). + * + * In case your reverse proxy uses static IP addresses, then you can add those + * addresses to the $trusted_proxies array. + * + * @param string[] $trusted_proxies The IP addresses of the proxy we want to trust. + */ + $trusted_proxies = apply_filters( 'rsa_trusted_proxies', array() ); + + if ( ! empty( $trusted_proxies ) ) { + foreach ( $trusted_proxies as $trusted_proxy ) { + // If REMOTE_ADDR is found in our trusted proxy, get IP from headers. + if ( self::ip_in_range( $remote_addr_header_ip, $trusted_proxy ) ) { + return self::get_ip_from_headers(); + } + } + + return ''; + } else { + return self::get_ip_from_headers(); + } + } + + /** + * Returns the first matched IP from the list of array of headers. + * + * @return string + */ + public static function get_ip_from_headers() { + $ip = ''; + $trusted_headers = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', @@ -1538,21 +1581,54 @@ public static function get_client_ip_address() { 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', - 'REMOTE_ADDR', ); - foreach ( $headers as $key ) { - if ( ! isset( $_SERVER[ $key ] ) ) { + /** + * Filter hook to set array of trusted IP address headers. + * + * Most CDN providers will set the IP address of the client in a number + * of headers. This allows the plugin to detect the IP address of the client + * even if it is behind a proxy. + * + * Use this hook to modify the permitted proxy headers. For sites without a + * CDN (or local proxy) it is recommended to add a filter to this hook to + * return an empty array. + * + * add_filter( 'rsa_trusted_headers', '__return_empty_array' ); + * + * By default, the following headers are trusted: + * - HTTP_CF_CONNECTING_IP + * - HTTP_CLIENT_IP + * - HTTP_X_FORWARDED_FOR + * - HTTP_X_FORWARDED + * - HTTP_X_CLUSTER_CLIENT_IP + * - HTTP_FORWARDED_FOR + * - HTTP_FORWARDED + * + * To allow for CDNs, these headers take priority over the REMOTE_ADDR value. + * + * @param string[] $trusted_proxies Array of trusted IP Address headers. + */ + $trusted_headers = apply_filters( 'rsa_trusted_headers', $trusted_headers ); + + // Add the REMOTE_ADDR value to the end of the array. + $trusted_headers[] = 'REMOTE_ADDR'; + + foreach ( array_unique( $trusted_headers ) as $header ) { + if ( ! isset( $_SERVER[ $header ] ) ) { continue; } foreach ( explode( ',', - sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) ) + sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) ) as $ip ) { $ip = trim( $ip ); // just to be safe. - if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) { + /** Hook to filter IP flags. */ + $filter_flags = apply_filters( 'rsa_get_client_ip_address_filter_flags', FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); + + if ( filter_var( $ip, FILTER_VALIDATE_IP, $filter_flags ) !== false ) { return $ip; } } diff --git a/tests/php/test-ip-addresses.php b/tests/php/test-ip-addresses.php index 50b299a3..a411c860 100644 --- a/tests/php/test-ip-addresses.php +++ b/tests/php/test-ip-addresses.php @@ -71,4 +71,118 @@ public function test_get_client_ip_address() { unset( $_SERVER[ $header ] ); } } + + /** + * Test trusted proxies. + * + * @dataProvider trusted_proxy_provider + * + * @param string $remote_ip Remote IP address. + * @param array $proxies Proxies to trust. + */ + public function test_rsa_trusted_proxies( string $remote_ip = '', array $proxies = array() ) { + $rsa = Restricted_Site_Access::get_instance(); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + add_filter( 'rsa_trusted_proxies', function() use ( $proxies ) { + return $proxies; + } ); + + $this->assertSame( $remote_ip, $rsa::get_client_ip_address() ); + + unset( $_SERVER['REMOTE_ADDR'] ); + } + + public function trusted_proxy_provider() { + /** + * Data to use in our trusted proxy tests + * + * First key is a string containing our REMOTE_ADDR IP. + * Second is an array of proxy IP addresses. + */ + return array( + // Test that if the REMOTE_ADDR matches our proxy, we return a proper IP. + array( '127.0.0.1', array( '127.0.0.1/24' ) ), + // Test that if the REMOTE_ADDR doesn't match our proxy, we return an empty string. + array( '', array( '10.0.0.0/8' ) ), + // Test if we have multiple proxies and one matches, we return a proper IP. + array( '127.0.0.1', array( '10.0.0.0/8', '127.0.0.1' ) ), + ); + } + + /** + * Test trusted headers + * + * @dataProvider trusted_headers_provider + * + * @param string $remote_ip Remote IP address + * @param array $headers Headers to set. + * @param array $trusted_headers Headers we want to trust. + */ + public function test_rsa_trusted_headers( string $remote_ip = '', array $headers = array(), array $trusted_headers = array() ) { + $rsa = Restricted_Site_Access::get_instance(); + + add_filter( 'rsa_get_client_ip_address_filter_flags', function() { + return FILTER_FLAG_NO_RES_RANGE; + } ); + + add_filter( 'rsa_trusted_headers', function() use ( $trusted_headers ) { + return $trusted_headers; + } ); + + foreach( $headers as $header => $ip ) { + $_SERVER[ $header ] = $ip; + } + + $this->assertSame( $remote_ip, $rsa::get_ip_from_headers() ); + + foreach( $headers as $header ) { + unset( $_SERVER[ $header ] ); + } + } + + public function trusted_headers_provider() { + /** + * Data to use in our trusted header tests + * + * First key is a string containing our expected IP. + * Second is an array of headers and the IP they are set to. + * Third is an array of headers to trust. + */ + return array( + // Test that if we don't trust any headers, we get the REMOTE_ADDR value. + array( + '127.0.0.1', + array( + 'HTTP_CLIENT_IP' => '10.0.0.0', + 'REMOTE_ADDR' => '127.0.0.1', + ), + array() + ), + // Test if we trust a single header, we get that value back. + array( + '10.0.0.0', + array( + 'HTTP_CLIENT_IP' => '10.0.0.0', + 'REMOTE_ADDR' => '127.0.0.1', + ), + array( 'HTTP_CLIENT_IP' ) + ), + // Test if we trust multiple headers, we get the first matched value back. + array( + '10.0.0.8', + array( + 'HTTP_FORWARDED' => '10.0.0.0', + 'HTTP_X_FORWARDED' => '10.0.0.8', + 'REMOTE_ADDR' => '127.0.0.1', + ), + array( + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED', + ) + ), + ); + } + }