diff --git a/WP_Auth0.php b/WP_Auth0.php index 6e4cad42..a8ce91fe 100644 --- a/WP_Auth0.php +++ b/WP_Auth0.php @@ -9,7 +9,7 @@ */ define( 'WPA0_VERSION', '3.8.1' ); -define( 'AUTH0_DB_VERSION', 19 ); +define( 'AUTH0_DB_VERSION', 20 ); define( 'WPA0_PLUGIN_FILE', __FILE__ ); define( 'WPA0_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); diff --git a/lib/WP_Auth0_DBManager.php b/lib/WP_Auth0_DBManager.php index 4598851c..4e3ee699 100644 --- a/lib/WP_Auth0_DBManager.php +++ b/lib/WP_Auth0_DBManager.php @@ -64,7 +64,7 @@ public function install_db( $version_to_install = null, $app_token = '' ) { } } - if ( $this->current_db_version < 9 ) { + if ( ( $this->current_db_version < 9 && 0 !== $this->current_db_version ) || 9 === $version_to_install ) { $this->migrate_users_data(); } @@ -81,17 +81,6 @@ public function install_db( $version_to_install = null, $app_token = '' ) { } } - if ( $this->current_db_version < 13 ) { - $ips = $options->get( 'migration_ips' ); - $oldips = '138.91.154.99,54.221.228.15,54.183.64.135,54.67.77.38,54.67.15.170,54.183.204.205,54.173.21.107,54.85.173.28'; - - $ipCheck = new WP_Auth0_Ip_Check( $options ); - - if ( $ips === $oldips ) { - $options->set( 'migration_ips', $ipCheck->get_ip_by_region( 'us' ) ); - } - } - if ( $this->current_db_version < 14 && is_null( $options->get( 'client_secret_b64_encoded' ) ) ) { if ( $options->get( 'client_id' ) ) { $options->set( 'client_secret_b64_encoded', true ); @@ -302,6 +291,20 @@ public function install_db( $version_to_install = null, $app_token = '' ) { } } + // 3.9.0 + if ( ( $this->current_db_version < 20 && 0 !== $this->current_db_version ) || 20 === $version_to_install ) { + + // Remove default IP addresses from saved field. + $migration_ips = trim( $options->get( 'migration_ips' ) ); + if ( $migration_ips ) { + $migration_ips = array_map( 'trim', explode( ',', $migration_ips ) ); + $ip_check = new WP_Auth0_Ip_Check( $options ); + $default_ips = explode( ',', $ip_check->get_ips_by_domain() ); + $custom_ips = array_diff( $migration_ips, $default_ips ); + $options->set( 'migration_ips', implode( ',', $custom_ips ) ); + } + } + $this->current_db_version = AUTH0_DB_VERSION; update_option( 'auth0_db_version', AUTH0_DB_VERSION ); diff --git a/lib/WP_Auth0_Ip_Check.php b/lib/WP_Auth0_Ip_Check.php index 83267dc7..bb00b189 100644 --- a/lib/WP_Auth0_Ip_Check.php +++ b/lib/WP_Auth0_Ip_Check.php @@ -1,6 +1,20 @@ a0_options = $a0_options; @@ -83,24 +97,35 @@ public function __construct( WP_Auth0_Options $a0_options = null ) { * Get regional inbound IP addresses based on a domain. * * @param string $domain - Tenant domain. + * @param string $glue - String used to implode arrays. * * @return string */ - public function get_ips_by_domain( $domain ) { - return $this->get_ip_by_region( WP_Auth0::get_tenant_region( $domain ) ); + public function get_ips_by_domain( $domain = null, $glue = self::IP_STRING_GLUE ) { + if ( empty( $domain ) ) { + $domain = $this->a0_options->get( 'domain' ); + } + $region = WP_Auth0::get_tenant_region( $domain ); + return $this->get_ip_by_region( $region, $glue ); } /** * Get regional inbound IP addresses based on a region. * * @param string $region - Tenant region. + * @param string $glue - String used to implode arrays. * * @return string */ - public function get_ip_by_region( $region ) { - return implode( ',', $this->valid_webtask_ips[ $region ] ); + public function get_ip_by_region( $region, $glue = self::IP_STRING_GLUE ) { + return implode( $glue, $this->valid_webtask_ips[ $region ] ); } + /** + * Get the IP address of the incoming connection. + * + * @return string + */ protected function get_request_ip() { $valid_proxy_ip = $this->a0_options->get( 'valid_proxy_ip' ); @@ -108,15 +133,20 @@ protected function get_request_ip() { if ( $_SERVER['REMOTE_ADDR'] == $valid_proxy_ip ) { return $_SERVER['HTTP_X_FORWARDED_FOR']; } - } else { - return $_SERVER['REMOTE_ADDR']; } - return null; + return $_SERVER['REMOTE_ADDR']; } + /** + * Process an array or concatenated string of IP addresses into ranges. + * + * @param array|string $ip_list - IP list to process. + * + * @return array + */ protected function process_ip_list( $ip_list ) { - $raw = explode( ',', $ip_list ); + $raw = is_array( $ip_list ) ? $ip_list : explode( self::IP_STRING_GLUE, $ip_list ); $ranges = array(); foreach ( $raw as $r ) { @@ -137,13 +167,21 @@ protected function process_ip_list( $ip_list ) { return $ranges; } - public function connection_is_valid( $valid_ips ) { - $ip = $this->get_request_ip(); - $valid_ip_ranges = $this->process_ip_list( $valid_ips ); + /** + * Check incoming IP address against default Auth0 and custom ones. + * + * @param string $valid_ips - String of comma-separated IP addresses to allow. + * + * @return bool + */ + public function connection_is_valid( $valid_ips = '' ) { + $valid_ips = explode( self::IP_STRING_GLUE, $valid_ips ); + $default_ips = explode( self::IP_STRING_GLUE, $this->get_ips_by_domain() ); + $allowed_ips = array_merge( $valid_ips, $default_ips ); + $allowed_ips = array_unique( $allowed_ips ); - foreach ( $valid_ip_ranges as $range ) { - $in_range = $this->in_range( $ip, $range ); - if ( $in_range ) { + foreach ( $this->process_ip_list( $allowed_ips ) as $range ) { + if ( $this->in_range( $this->get_request_ip(), $range ) ) { return true; } } @@ -151,7 +189,29 @@ public function connection_is_valid( $valid_ips ) { return false; } + /** + * Check if an IP address is within a range. + * + * @param string $ip - IP address to check. + * @param array $range - IP range to use. + * + * @return bool + */ + private function in_range( $ip, array $range ) { + $from = ip2long( $range['from'] ); + $to = ip2long( $range['to'] ); + $ip = ip2long( $ip ); + + return $ip >= $from && $ip <= $to; + } + + // phpcs:disable + /** + * TODO: Deprecate, not used. Also remove related setting. + * + * @codeCoverageIgnore + */ public function init() { if ( ! WP_Auth0_Options::Instance()->get( 'ip_range_check' ) || is_admin() ) { return; @@ -160,6 +220,11 @@ public function init() { add_filter( 'wp_auth0_get_option', array( $this, 'check_activate' ), 10, 2 ); } + /** + * TODO: Deprecate, not used. + * + * @codeCoverageIgnore + */ public function check_activate( $val, $key ) { if ( 'active' !== $key ) { return $val; @@ -168,6 +233,11 @@ public function check_activate( $val, $key ) { return $is_active; } + /** + * TODO: Deprecate, not used. + * + * @codeCoverageIgnore + */ private function validate_ip() { $ranges = $this->get_ranges(); $ip = $_SERVER['REMOTE_ADDR']; @@ -182,14 +252,11 @@ private function validate_ip() { return false; } - private function in_range( $ip, $range ) { - $from = ip2long( $range['from'] ); - $to = ip2long( $range['to'] ); - $ip = ip2long( $ip ); - - return $ip >= $from && $ip <= $to; - } - + /** + * TODO: Deprecate, not used. Also remove related setting. + * + * @codeCoverageIgnore + */ private function get_ranges() { $data = WP_Auth0_Options::Instance()->get( 'ip_ranges' ); $data = str_replace( "\r\n", "\n", $data ); @@ -215,4 +282,6 @@ private function get_ranges() { return $ranges; } + + // phpcs:enable } diff --git a/lib/WP_Auth0_Options_Generic.php b/lib/WP_Auth0_Options_Generic.php index 93a54b3f..60ccd3c7 100644 --- a/lib/WP_Auth0_Options_Generic.php +++ b/lib/WP_Auth0_Options_Generic.php @@ -199,6 +199,15 @@ public function delete() { return delete_option( $this->_options_name ); } + /** + * Reset options to defaults. + */ + public function reset() { + $this->_opts = null; + $this->delete(); + $this->get_options(); + } + /** * Return default options as key => value or just keys. * diff --git a/lib/admin/WP_Auth0_Admin_Advanced.php b/lib/admin/WP_Auth0_Admin_Advanced.php index 2499bab4..43dc7423 100644 --- a/lib/admin/WP_Auth0_Admin_Advanced.php +++ b/lib/admin/WP_Auth0_Admin_Advanced.php @@ -105,7 +105,7 @@ public function init() { 'function' => 'render_migration_ws_ips_filter', ), array( - 'name' => __( 'IP Addresses', 'wp-auth0' ), + 'name' => '', 'opt' => 'migration_ips', 'id' => 'wpa0_migration_ws_ips', 'function' => 'render_migration_ws_ips', @@ -419,10 +419,13 @@ public function render_migration_ws_ips_filter( $args = array() ) { * @see add_settings_field() */ public function render_migration_ws_ips( $args = array() ) { + $ip_check = new WP_Auth0_Ip_Check( WP_Auth0_Options::Instance() ); $this->render_textarea_field( $args['label_for'], $args['opt_name'] ); $this->render_field_description( - __( 'Only requests from these IPs will be allowed to access the migration webservice. ', 'wp-auth0' ) . - __( 'Separate multiple IPs with commas', 'wp-auth0' ) + __( 'Only requests from these IPs will be allowed to access the migration endpoints. ', 'wp-auth0' ) . + __( 'Separate multiple IPs with commas. ', 'wp-auth0' ) . + __( 'The following Auth0 IPs are automatically whitelisted: ', 'wp-auth0' ) . + '

' . $ip_check->get_ips_by_domain( null, ' ' ) . '' ); } diff --git a/lib/admin/WP_Auth0_Admin_Generic.php b/lib/admin/WP_Auth0_Admin_Generic.php index dc8b6bd1..399b62d2 100644 --- a/lib/admin/WP_Auth0_Admin_Generic.php +++ b/lib/admin/WP_Auth0_Admin_Generic.php @@ -231,7 +231,8 @@ protected function render_radio_buttons( array $buttons, $id, $input_name, $curr * @param string $text - description text to display */ protected function render_field_description( $text ) { - printf( '
%s.
', $text ); + $period = ! in_array( $text[ strlen( $text ) - 1 ], array( '.', ':', '>' ) ) ? '.' : ''; + printf( '
%s%s
', $text, $period ); } /** diff --git a/tests/testIpCheck.php b/tests/testIpCheck.php index 7a74271c..de2a4dbc 100644 --- a/tests/testIpCheck.php +++ b/tests/testIpCheck.php @@ -14,14 +14,48 @@ */ class TestIpCheck extends TestCase { - use setUpTestDb; + use setUpTestDb { + setUp as setUpDb; + } + + /** + * Instance of WP_Auth0_Options. + * + * @var WP_Auth0_Options + */ + public static $opts; + + /** + * Original request IP address. + * + * @var string + */ + public static $backup_ip; + + /** + * Run before the test suite. + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + self::$opts = WP_Auth0_Options::Instance(); + self::$backup_ip = $_SERVER['REMOTE_ADDR']; + } + + /** + * Runs before each test method. + */ + public function setUp() { + $_SERVER['REMOTE_ADDR'] = self::$backup_ip; + parent::setUp(); + $this->setUpDb(); + self::$opts->reset(); + } /** * Test that a specific region and domain return the correct number of IP addresses. */ - public function testGetIpByDomain() { - $opts = WP_Auth0_Options::Instance(); - $ip_check = new WP_Auth0_Ip_Check( $opts ); + public function testThatIpCountDidNotChange() { + $ip_check = new WP_Auth0_Ip_Check( self::$opts ); $us_ips = explode( ',', $ip_check->get_ip_by_region( 'us' ) ); $this->assertCount( 16, $us_ips ); @@ -37,5 +71,70 @@ public function testGetIpByDomain() { $this->assertCount( 11, $au_ips ); $au_ips = explode( ',', $ip_check->get_ips_by_domain( 'test.au.auth0.com' ) ); $this->assertCount( 11, $au_ips ); + + $us_ips = $ip_check->get_ip_by_region( 'us' ); + self::$opts->set( 'domain', 'test.auth0.com' ); + $domain_ips = $ip_check->get_ips_by_domain(); + $this->assertEquals( $us_ips, $domain_ips ); + } + + /** + * Test that unauthorized URLs are not allowed. + */ + public function testThatInvalidConnectionsAreNotAllowed() { + $ip_check = new WP_Auth0_Ip_Check( self::$opts ); + + $_SERVER['REMOTE_ADDR'] = '1.2.3.4'; + $this->assertFalse( $ip_check->connection_is_valid() ); + $this->assertFalse( $ip_check->connection_is_valid( '4.3.2.1' ) ); + + self::$opts->set( 'domain', 'test.auth0.com' ); + $this->assertFalse( $ip_check->connection_is_valid() ); + $this->assertFalse( $ip_check->connection_is_valid( '4.3.2.1' ) ); + } + + /** + * Test that authorized IPs are allowed. + */ + public function testThatValidConnectionsAreAllowed() { + $ip_check = new WP_Auth0_Ip_Check( self::$opts ); + + $_SERVER['REMOTE_ADDR'] = '1.2.3.4'; + $this->assertTrue( $ip_check->connection_is_valid( '1.2.3.4' ) ); + $this->assertTrue( $ip_check->connection_is_valid( '1.2.3.0 - 1.2.3.10' ) ); + } + + /** + * Test that the default Auth0 IPs are always allowed + */ + public function testThatDefaultConnectionsAreAllowed() { + $ip_check = new WP_Auth0_Ip_Check( self::$opts ); + + $_SERVER['REMOTE_ADDR'] = '34.195.142.251'; + self::$opts->set( 'domain', 'test.auth0.com' ); + $this->assertTrue( $ip_check->connection_is_valid() ); + $this->assertTrue( $ip_check->connection_is_valid( '1.2.3.4' ) ); + + $_SERVER['REMOTE_ADDR'] = '34.253.4.94'; + self::$opts->set( 'domain', 'test.eu.auth0.com' ); + $this->assertTrue( $ip_check->connection_is_valid() ); + $this->assertTrue( $ip_check->connection_is_valid( '1.2.3.4' ) ); + + $_SERVER['REMOTE_ADDR'] = '13.54.254.182'; + self::$opts->set( 'domain', 'test.au.auth0.com' ); + $this->assertTrue( $ip_check->connection_is_valid() ); + $this->assertTrue( $ip_check->connection_is_valid( '1.2.3.4' ) ); + } + + /** + * Test that a proxy IP address can be used. + */ + public function testThatProxyConnectionsAreAllowed() { + $ip_check = new WP_Auth0_Ip_Check( self::$opts ); + + self::$opts->set( 'valid_proxy_ip', '1.2.3.4' ); + $_SERVER['REMOTE_ADDR'] = '1.2.3.4'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '2.3.4.5'; + $this->assertTrue( $ip_check->connection_is_valid( '2.3.4.5' ) ); } } diff --git a/tests/testWPAuth0DbMigrations.php b/tests/testWPAuth0DbMigrations.php index 33c000d3..f830f15e 100644 --- a/tests/testWPAuth0DbMigrations.php +++ b/tests/testWPAuth0DbMigrations.php @@ -17,11 +17,25 @@ class TestWPAuth0DbMigrations extends TestCase { use setUpTestDb; /** - * Test the basic options functionality. + * Instance of WP_Auth0_Options. + * + * @var WP_Auth0_Options */ - public function testV19Update() { - $opts = new WP_Auth0_Options(); + public static $opts; + + /** + * Setup for entire test class. + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + self::$opts = WP_Auth0_Options::Instance(); + } + /** + * Test a DB upgrade from v18 to v19. + */ + public function testV19Update() { + $test_version = 19; $initial_connections = [ 'social_twitter_key' => '__twitter_key_test__', 'social_twitter_secret' => '__twitter_secret_test__', @@ -32,22 +46,54 @@ public function testV19Update() { $connection_keys = array_keys( $initial_connections ); // Save a 'connections' settings array. - $opts->set( 'connections', $initial_connections ); - $saved_connections = $opts->get( 'connections' ); + self::$opts->set( 'connections', $initial_connections ); + $saved_connections = self::$opts->get( 'connections' ); $this->assertEquals( $initial_connections, $saved_connections ); // Run the migration for v19. - update_option( 'auth0_db_version', 18 ); - $db_manager = new WP_Auth0_DBManager( $opts ); + update_option( 'auth0_db_version', $test_version - 1 ); + $db_manager = new WP_Auth0_DBManager( self::$opts ); $db_manager->init(); foreach ( $connection_keys as $key ) { - $this->assertEmpty( $opts->get( $key ) ); + $this->assertEmpty( self::$opts->get( $key ) ); } - $db_manager->install_db( 19, null ); + $db_manager->install_db( $test_version, null ); + foreach ( $connection_keys as $key ) { - $this->assertEquals( $initial_connections[ $key ], $opts->get( $key ) ); + $this->assertEquals( $initial_connections[ $key ], self::$opts->get( $key ) ); } } + + /** + * Test a DB upgrade from v19 to v20. + */ + public function testV20Update() { + $test_version = 20; + + update_option( 'auth0_db_version', $test_version - 1 ); + $db_manager = new WP_Auth0_DBManager( self::$opts ); + $db_manager->init(); + + // Set a US domain. + self::$opts->set( 'domain', 'test.auth0.com' ); + + // Save custom IPs, default IPs for US, and default IPs for other regions. + self::$opts->set( 'migration_ips', '1.2.3.4,2.3.4.5,34.195.142.251,35.160.3.103,34.253.4.94,13.54.254.182' ); + + // Run the update. + $db_manager->install_db( $test_version, null ); + + // Check that the correct IPs were removed. + $remaining_ips = explode( ',', self::$opts->get( 'migration_ips' ) ); + + $this->assertContains( '1.2.3.4', $remaining_ips ); + $this->assertContains( '2.3.4.5', $remaining_ips ); + $this->assertContains( '34.253.4.94', $remaining_ips ); + $this->assertContains( '13.54.254.182', $remaining_ips ); + + $this->assertNotContains( '34.195.142.251', $remaining_ips ); + $this->assertNotContains( '35.160.3.103', $remaining_ips ); + } }