diff --git a/lib/WP_Auth0_Routes.php b/lib/WP_Auth0_Routes.php index 5679c3c6..0363f4d1 100755 --- a/lib/WP_Auth0_Routes.php +++ b/lib/WP_Auth0_Routes.php @@ -1,5 +1,16 @@ query_vars['a0_action']; } - if ( $page === null && isset( $wp->query_vars['pagename'] ) ) { + if ( null === $page && isset( $wp->query_vars['pagename'] ) ) { $page = $wp->query_vars['pagename']; } @@ -64,8 +82,8 @@ public function custom_requests( $wp ) { $output = $this->migration_ws_login(); break; case 'migration-ws-get-user': - $this->migration_ws_get_user(); - exit; + $output = $this->migration_ws_get_user(); + break; case 'coo-fallback': $this->coo_fallback(); exit; @@ -130,22 +148,25 @@ protected function getAuthorizationHeader() { return $authorization; } + /** + * User migration login route used by custom database Login script. + * + * @return array + * + * @see lib/scripts-js/db-login.js + */ protected function migration_ws_login() { + // Migration web service is not turned on. if ( $this->a0_options->get( 'migration_ws' ) == 0 ) { - return array( - 'status' => 403, - 'error' => __( 'Forbidden', 'wp-auth0' ), - ); + return $this->error_return_array( 403 ); } + // IP filtering is on and incoming IP address does not match filter. if ( $this->a0_options->get( 'migration_ips_filter' ) ) { $allowed_ips = $this->a0_options->get( 'migration_ips' ); if ( ! $this->ip_check->connection_is_valid( $allowed_ips ) ) { - return array( - 'status' => 401, - 'error' => __( 'Unauthorized', 'wp-auth0' ), - ); + return $this->error_return_array( 401 ); } } @@ -192,16 +213,25 @@ protected function migration_ws_login() { } } + /** + * User migration get user route used by custom database Login script. + * + * @return array + * + * @see lib/scripts-js/db-get-user.js + */ protected function migration_ws_get_user() { + // Migration web service is not turned on. if ( $this->a0_options->get( 'migration_ws' ) == 0 ) { - return; + return $this->error_return_array( 403 ); } + // IP filtering is on and incoming IP address does not match filter. if ( $this->a0_options->get( 'migration_ips_filter' ) ) { - $ipCheck = new WP_Auth0_Ip_Check( $this->a0_options ); - if ( ! $ipCheck->connection_is_valid( $this->a0_options->get( 'migration_ips' ) ) ) { - return; + $allowed_ips = $this->a0_options->get( 'migration_ips' ); + if ( ! $this->ip_check->connection_is_valid( $allowed_ips ) ) { + return $this->error_return_array( 401 ); } } @@ -215,13 +245,13 @@ protected function migration_ws_get_user() { try { if ( empty( $authorization ) ) { - throw new Exception( __( 'Unauthorized: missing authorization header', 'wp-auth0' ) ); + throw new Exception( __( 'Unauthorized: missing authorization header', 'wp-auth0' ), 401 ); } $token = JWT::decode( $authorization, $secret, array( 'HS256' ) ); if ( $token->jti != $token_id ) { - throw new Exception( __( 'Invalid token ID', 'wp-auth0' ) ); + throw new Exception( __( 'Invalid token ID', 'wp-auth0' ), 401 ); } if ( ! isset( $_POST['username'] ) ) { @@ -231,32 +261,26 @@ protected function migration_ws_get_user() { $username = $_POST['username']; $user = get_user_by( 'email', $username ); - if ( ! $user ) { $user = get_user_by( 'slug', $username ); } - if ( $user instanceof WP_Error ) { - WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__, $user->get_error_message() ); - $user = array( 'error' => __( 'Invalid credentials', 'wp-auth0' ) ); - } else { - - if ( ! $user instanceof WP_User ) { - $user = array( 'error' => __( 'Invalid credentials', 'wp-auth0' ) ); - } else { - unset( $user->data->user_pass ); - $user = apply_filters( 'auth0_migration_ws_authenticated', $user ); - } + if ( ! $user ) { + throw new Exception( __( 'Invalid Credentials', 'wp-auth0' ), 401 ); } + + unset( $user->data->user_pass ); + return apply_filters( 'auth0_migration_ws_authenticated', $user ); + } catch ( Exception $e ) { WP_Auth0_ErrorManager::insert_auth0_error( __METHOD__, $e ); - $user = array( 'error' => $e->getMessage() ); + return array( + 'status' => $e->getCode() ?: 400, + 'error' => $e->getMessage(), + ); } - - echo json_encode( $user ); - exit; - } + protected function oauth2_config() { $callback_url = admin_url( 'admin.php?page=wpa0-setup&callback=1' ); @@ -271,4 +295,28 @@ protected function oauth2_config() { ); exit; } + + /** + * Default error arrays. + * + * @param integer $code - Error code. + * + * @return array + */ + private function error_return_array( $code ) { + + switch ( $code ) { + case 401: + return array( + 'status' => 401, + 'error' => __( 'Unauthorized', 'wp-auth0' ), + ); + + case 403: + return array( + 'status' => 403, + 'error' => __( 'Forbidden', 'wp-auth0' ), + ); + } + } } diff --git a/tests/testRoutes.php b/tests/testRoutes.php new file mode 100644 index 00000000..4f8d7e58 --- /dev/null +++ b/tests/testRoutes.php @@ -0,0 +1,107 @@ + [ 'custom_requests_return' => true ] ]; + + /** + * Instance of WP_Auth0_Options. + * + * @var WP_Auth0_Options + */ + public static $opts; + + /** + * Instance of WP_Auth0_Routes. + * + * @var WP_Auth0_Routes + */ + public static $routes; + + /** + * WP_Auth0_ErrorLog instance. + * + * @var WP_Auth0_ErrorLog + */ + protected static $error_log; + + /** + * Mock WP instance. + * + * @var stdClass|WP_Query + */ + protected static $wp; + + /** + * Run before test suite. + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + self::$opts = WP_Auth0_Options::Instance(); + self::$routes = new WP_Auth0_Routes( self::$opts ); + + self::$error_log = new WP_Auth0_ErrorLog(); + self::$wp = (object) self::WP_OBJECT_DEFAULT; + } + + /** + * Runs before each test method. + */ + public function setUp() { + parent::setUp(); + $this->setUpDb(); + self::$wp = (object) self::WP_OBJECT_DEFAULT; + } + + /** + * Runs after each test method. + */ + public function tearDown() { + parent::tearDown(); + self::$error_log->clear(); + } + + /** + * If we have no query vars, the route should do nothing. + */ + public function testThatEmptyQueryVarsDoesNothing() { + $this->assertNull( self::$routes->custom_requests( self::$wp ) ); + } + + /** + * If we have no valid query vars, the route should do nothing. + */ + public function testThatUnknownRouteDoesNothing() { + self::$wp->query_vars['a0_action'] = uniqid(); + $this->assertFalse( self::$routes->custom_requests( self::$wp ) ); + + unset( self::$wp->query_vars['a0_action'] ); + self::$wp->query_vars['pagename'] = uniqid(); + $this->assertFalse( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEmpty( self::$error_log->get() ); + } +} diff --git a/tests/testRoutesGetUser.php b/tests/testRoutesGetUser.php new file mode 100644 index 00000000..fdff2bb1 --- /dev/null +++ b/tests/testRoutesGetUser.php @@ -0,0 +1,258 @@ + [ 'custom_requests_return' => true ] ]; + + /** + * Instance of WP_Auth0_Options. + * + * @var WP_Auth0_Options + */ + public static $opts; + + /** + * Instance of WP_Auth0_Routes. + * + * @var WP_Auth0_Routes + */ + public static $routes; + + /** + * WP_Auth0_ErrorLog instance. + * + * @var WP_Auth0_ErrorLog + */ + protected static $error_log; + + /** + * Mock WP instance. + * + * @var stdClass|WP_Query + */ + protected static $wp; + + /** + * Run before test suite. + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + self::$opts = WP_Auth0_Options::Instance(); + self::$routes = new WP_Auth0_Routes( self::$opts ); + + self::$error_log = new WP_Auth0_ErrorLog(); + self::$wp = (object) self::WP_OBJECT_DEFAULT; + } + + /** + * Runs before each test method. + */ + public function setUp() { + $_POST = []; + $_GET = []; + $_REQUEST = []; + + parent::setUp(); + $this->setUpDb(); + self::$opts->reset(); + self::$wp = (object) self::WP_OBJECT_DEFAULT; + } + + /** + * Runs after each test method. + */ + public function tearDown() { + parent::tearDown(); + self::$error_log->clear(); + } + + /** + * If migration services are off, the route should fail with an error. + */ + public function testThatGetUserRouteIsForbiddenByDefault() { + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 403, $output->status ); + $this->assertEquals( 'Forbidden', $output->error ); + + $this->assertEmpty( self::$error_log->get() ); + } + + /** + * If the incoming IP address is invalid, the route should fail with an error. + */ + public function testThatGetUserRouteIsUnauthorizedIfWrongIp() { + self::$opts->set( 'migration_ws', 1 ); + self::$opts->set( 'migration_ips_filter', 1 ); + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 401, $output->status ); + $this->assertEquals( 'Unauthorized', $output->error ); + + $this->assertEmpty( self::$error_log->get() ); + } + + /** + * If there is no token, the route should fail with an error. + */ + public function testThatGetUserRouteIsUnauthorizedIfNoToken() { + self::$opts->set( 'migration_ws', 1 ); + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 401, $output->status ); + $this->assertEquals( 'Unauthorized: missing authorization header', $output->error ); + + $log = self::$error_log->get(); + $this->assertCount( 1, $log ); + $this->assertEquals( $output->error, $log[0]['message'] ); + } + + /** + * If the token is invalid, the route should fail with an error. + */ + public function testThatGetUserRouteIsUnauthorizedIfBadToken() { + self::$opts->set( 'migration_ws', 1 ); + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + $_POST['access_token'] = uniqid(); + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 400, $output->status ); + $this->assertEquals( 'Key may not be empty', $output->error ); + + $log = self::$error_log->get(); + $this->assertCount( 1, $log ); + $this->assertEquals( $output->error, $log[0]['message'] ); + } + + /** + * If the token has the wrong JTI, the route should fail with an error. + */ + public function testThatGetUserRouteIsUnauthorizedIfWrongJti() { + $client_secret = '__test_client_secret__'; + self::$opts->set( 'migration_ws', 1 ); + self::$opts->set( 'client_secret', $client_secret ); + self::$opts->set( 'migration_token_id', '__test_token_id__' ); + + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + $_POST['access_token'] = JWT::encode( [ 'jti' => uniqid() ], $client_secret ); + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 401, $output->status ); + $this->assertEquals( 'Invalid token ID', $output->error ); + + $log = self::$error_log->get(); + $this->assertCount( 1, $log ); + $this->assertEquals( $output->error, $log[0]['message'] ); + } + + /** + * If there is no username POSTed, the route should fail with an error. + */ + public function testThatGetUserRouteIsBadRequestIfNoUsername() { + $client_secret = '__test_client_secret__'; + $token_id = '__test_token_id__'; + self::$opts->set( 'migration_ws', 1 ); + self::$opts->set( 'client_secret', $client_secret ); + self::$opts->set( 'migration_token_id', $token_id ); + + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + $_POST['access_token'] = JWT::encode( [ 'jti' => $token_id ], $client_secret ); + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 400, $output->status ); + $this->assertEquals( 'Username is required', $output->error ); + + $log = self::$error_log->get(); + $this->assertCount( 1, $log ); + $this->assertEquals( $output->error, $log[0]['message'] ); + } + + /** + * If there the username cannot be found, the route should fail with an error. + */ + public function testThatGetUserRouteIsUnauthorizedIfUserNotFound() { + $client_secret = '__test_client_secret__'; + $token_id = '__test_token_id__'; + self::$opts->set( 'migration_ws', 1 ); + self::$opts->set( 'client_secret', $client_secret ); + self::$opts->set( 'migration_token_id', $token_id ); + + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + + $_POST['access_token'] = JWT::encode( [ 'jti' => $token_id ], $client_secret ); + $_POST['username'] = uniqid(); + + $output = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( 401, $output->status ); + $this->assertEquals( 'Invalid Credentials', $output->error ); + + $log = self::$error_log->get(); + $this->assertCount( 1, $log ); + $this->assertEquals( $output->error, $log[0]['message'] ); + } + + /** + * Route should return a user with no password set if provided a valid username or email. + */ + public function testThatGetUserRouteReturnsUserIfSuccessful() { + $client_secret = '__test_client_secret__'; + $token_id = '__test_token_id__'; + $_POST['username'] = uniqid() . '@' . uniqid() . '.com'; + $user = $this->createUser( [ 'user_email' => $_POST['username'] ] ); + self::$opts->set( 'migration_ws', 1 ); + self::$opts->set( 'client_secret', $client_secret ); + self::$opts->set( 'migration_token_id', $token_id ); + + self::$wp->query_vars['a0_action'] = 'migration-ws-get-user'; + $_POST['access_token'] = JWT::encode( [ 'jti' => $token_id ], $client_secret ); + + $output_em = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( $user->ID, $output_em->data->ID ); + $this->assertEquals( $user->user_login, $output_em->data->user_login ); + $this->assertEquals( $user->user_email, $output_em->data->user_email ); + $this->assertEquals( $user->display_name, $output_em->data->display_name ); + $this->assertObjectNotHasAttribute( 'user_pass', $output_em->data ); + $this->assertEmpty( self::$error_log->get() ); + + // Test username lookup. + $_POST['username'] = $user->user_login; + $output_un = json_decode( self::$routes->custom_requests( self::$wp ) ); + + $this->assertEquals( $output_em, $output_un ); + $this->assertEmpty( self::$error_log->get() ); + } +} diff --git a/tests/testRoutesLogin.php b/tests/testRoutesLogin.php index 0882721f..ff95df41 100644 --- a/tests/testRoutesLogin.php +++ b/tests/testRoutesLogin.php @@ -51,7 +51,7 @@ class TestRoutesLogin extends TestCase { /** * Mock WP instance. * - * @var stdClass + * @var stdClass|WP_Query */ protected static $wp; @@ -89,27 +89,6 @@ public function tearDown() { self::$error_log->clear(); } - /** - * If we have no query vars, the route should do nothing. - */ - public function testThatEmptyQueryVarsDoesNothing() { - $this->assertNull( self::$routes->custom_requests( self::$wp ) ); - } - - /** - * If we have no valid query vars, the route should do nothing. - */ - public function testThatUnknownRouteDoesNothing() { - self::$wp->query_vars['a0_action'] = uniqid(); - $this->assertFalse( self::$routes->custom_requests( self::$wp ) ); - - unset( self::$wp->query_vars['a0_action'] ); - self::$wp->query_vars['pagename'] = uniqid(); - $this->assertFalse( self::$routes->custom_requests( self::$wp ) ); - - $this->assertEmpty( self::$error_log->get() ); - } - /** * If migration services are off, the route should fail with an error. */ @@ -144,7 +123,6 @@ public function testThatLoginRouteIsUnauthorizedIfWrongIp() { * If there is no token, the route should fail with an error. */ public function testThatLoginRouteIsUnauthorizedIfNoToken() { - $expected_msg = self::$opts->set( 'migration_ws', 1 ); self::$wp->query_vars['a0_action'] = 'migration-ws-login';