diff --git a/includes/API/Events.php b/includes/API/Events.php index 316822b..c9dd09f 100644 --- a/includes/API/Events.php +++ b/includes/API/Events.php @@ -43,7 +43,8 @@ public function __construct( HiiveConnection $hiive, EventManager $event_manager /** * Registers the routes for the objects of the controller. * - * @see register_rest_route() + * @see register_rest_route() + * @see EventManager::rest_api_init() */ public function register_routes() { diff --git a/includes/API/Verify.php b/includes/API/Verify.php index c383153..07fa841 100644 --- a/includes/API/Verify.php +++ b/includes/API/Verify.php @@ -3,7 +3,9 @@ namespace NewfoldLabs\WP\Module\Data\API; use NewfoldLabs\WP\Module\Data\HiiveConnection; +use WP_Error; use WP_REST_Controller; +use WP_REST_Request; use WP_REST_Server; use WP_REST_Response; @@ -37,6 +39,7 @@ public function __construct( HiiveConnection $hiive ) { * @since 4.7.0 * * @see register_rest_route() + * @see HiiveConnection::rest_api_init() */ public function register_routes() { @@ -53,11 +56,10 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'permission_callback' => '__return_true', ), ) ); - } /** @@ -65,8 +67,8 @@ public function register_routes() { * * @since 1.0 * - * @param \WP_REST_Request $request Full details about the request. - * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure. + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $valid = $this->hiive->verify_token( $request['token'] ); @@ -82,16 +84,4 @@ public function get_items( $request ) { return $response; } - - /** - * No authentication required for this endpoint - * - * @since 1.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error - */ - public function get_items_permissions_check( $request ) { - return true; - } } diff --git a/includes/Data.php b/includes/Data.php index e743046..310a746 100644 --- a/includes/Data.php +++ b/includes/Data.php @@ -43,7 +43,7 @@ public function start(): void { // Delays our primary module setup until init add_action( 'init', array( $this, 'init' ) ); - add_action( 'rest_authentication_errors', array( $this, 'authenticate' ) ); + add_filter( 'rest_authentication_errors', array( $this, 'authenticate' ) ); // If we ever get a 401 response from the Hiive API, delete the token. add_filter( @@ -102,22 +102,25 @@ public function init(): void { * Authenticate incoming REST API requests. * * @hooked rest_authentication_errors - * @see WP_REST_Server::check_authentication() * - * @param bool|null|\WP_Error $status + * @param bool|null|\WP_Error $errors * * @return bool|null|\WP_Error + * @see WP_REST_Server::check_authentication() + * + * @used-by ConnectSite::verifyToken() in Hiive. + * */ - public function authenticate( $status ) { + public function authenticate( $errors ) { // Make sure there wasn't a different authentication method used before this - if ( ! is_null( $status ) ) { - return $status; + if ( ! is_null( $errors ) ) { + return $errors; } // Make sure this is a REST API request if ( ! defined( 'REST_REQUEST' ) || ! constant( 'REST_REQUEST' ) ) { - return $status; + return $errors; } // If no auth header included, bail to allow a different auth method @@ -156,13 +159,13 @@ public function authenticate( $status ) { } if ( ! empty( $user ) && is_a( $user, \WP_User::class ) ) { - wp_set_current_user( $user->id ); + wp_set_current_user( $user->ID ); return true; } } // Don't return false, since we could be interfering with a basic auth implementation. - return null; + return $errors; } } diff --git a/tests/phpunit/includes/DataTest.php b/tests/phpunit/includes/DataTest.php new file mode 100644 index 0000000..24b6633 --- /dev/null +++ b/tests/phpunit/includes/DataTest.php @@ -0,0 +1,189 @@ +andReturnUsing(function($input) { + return json_encode($input); + }); + } + + public function tearDown(): void { + parent::tearDown(); + + \Patchwork\restoreAll(); + + unset($_SERVER['HTTP_AUTHORIZATION']); + } + + /** + * @covers ::authenticate + */ + public function test_authenticate() + { + $sut = new Data(); + + \Patchwork\redefine( + 'defined', + function ( string $constant_name ) { + switch ($constant_name) { + case 'REST_REQUEST': + return true; + default: + return \Patchwork\relay(func_get_args()); + } + } + ); + + \Patchwork\redefine( + 'constant', + function ( string $constant_name ) { + switch ($constant_name) { + case 'REST_REQUEST': + return true; + default: + return \Patchwork\relay(func_get_args()); + } + } + ); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + + \Patchwork\redefine( + 'file_get_contents', + function ( string $filename ) { + switch ($filename) { + case 'php://input': + return ''; + default: + return \Patchwork\relay(func_get_args()); + } + } + ); + + $_SERVER['HTTP_X-Timestamp'] = time(); + + $hiive_site_auth_token = 'abc123'; + + $hash = hash( 'sha256', json_encode(array( + 'method' => 'GET', + 'url' => 'http://', /** @see Url::getCurrentUrl() */ + 'body' => '', + 'timestamp' => null, +// 'timestamp' => $_SERVER['HTTP_X-Timestamp'], + ))); + $salt = hash( 'sha256', strrev( $hiive_site_auth_token ) ); + $token = hash( 'sha256', $hash . $salt ); + + $_SERVER['HTTP_AUTHORIZATION'] = "Bearer $token"; + + WP_Mock::userFunction('get_option') + ->with('nfd_data_token') + ->andReturn($hiive_site_auth_token); + + $user = Mockery::mock(\WP_User::class); + $user->ID = 123; + + WP_Mock::userFunction('get_users') + ->with(\WP_Mock\Functions::type('array')) + ->andReturn(array($user)); + + WP_Mock::userFunction('wp_set_current_user') + ->once() + ->with(123); + + $result = $sut->authenticate(null); + + self::assertTrue($result); + } + + /** + * @covers ::authenticate + */ + public function test_authenticate_returns_early_when_no_auth_header() + { + + $sut = new Data(); + + \Patchwork\redefine( + 'defined', + function ( string $constant_name ) { + switch ($constant_name) { + case 'REST_REQUEST': + return true; + default: + return \Patchwork\relay(func_get_args()); + } + } + ); + + \Patchwork\redefine( + 'constant', + function ( string $constant_name ) { + switch ($constant_name) { + case 'REST_REQUEST': + return true; + default: + return \Patchwork\relay(func_get_args()); + } + } + ); + + + $result = $sut->authenticate(null); + + self::assertNull($result); + + } + + /** + * @covers ::authenticate + */ + public function test_authenticate_returns_early_when_not_a_rest_request() + { + $sut = new Data(); + + \Patchwork\redefine( + 'defined', + function ( string $constant_name ) { + switch ($constant_name) { + case 'REST_REQUEST': + return false; + default: + return \Patchwork\relay(func_get_args()); + } + } + ); + + $result = $sut->authenticate(null); + + self::assertNull($result); + } + + /** + * @covers ::authenticate + */ + public function test_authenticate_returns_early_when_already_authenticated() + { + $sut = new Data(); + + $result = $sut->authenticate(true); + + self::assertTrue($result); + } +} diff --git a/tests/phpunit/includes/HiiveConnectionTest.php b/tests/phpunit/includes/HiiveConnectionTest.php index 8143608..8a53207 100644 --- a/tests/phpunit/includes/HiiveConnectionTest.php +++ b/tests/phpunit/includes/HiiveConnectionTest.php @@ -19,7 +19,7 @@ public function setUp(): void WP_Mock::passthruFunction('__'); - WP_Mock::userFunction('wp_json_encode')->andReturn(function($input) { + WP_Mock::userFunction('wp_json_encode')->andReturnUsing(function($input) { return json_encode($input); }); } diff --git a/tests/wpunit/includes/API/VerifyTest.php b/tests/wpunit/includes/API/VerifyTest.php new file mode 100644 index 0000000..4965a45 --- /dev/null +++ b/tests/wpunit/includes/API/VerifyTest.php @@ -0,0 +1,86 @@ +register_routes(); + + /** @var \Spy_REST_Server $rest_server */ + $rest_server = rest_get_server(); + $rest_routes = $rest_server->get_routes(); + + // MD5 hashes are 32 hexadecimals long. + self::assertArrayHasKey('/newfold-data/v1/verify/(?P[a-f0-9]{32})', $rest_routes); + } + + /** + * @covers ::get_items + */ + public function test_get_items() :void + { + $hiive_connection = \Mockery::mock( \NewfoldLabs\WP\Module\Data\HiiveConnection::class ); + + $hiive_connection->shouldReceive('verify_token') + ->with('a-valid-token') + ->andReturnTrue(); + + $sut = new Verify( $hiive_connection ); + + $request = new \WP_REST_Request(); + $request->set_param( 'token', 'a-valid-token' ); + + $result = $sut->get_items( $request ); + + self::assertEquals(200, $result->status); + } + + /** + * @covers ::get_items + */ + public function test_get_items_invalid() :void + { + $hiive_connection = \Mockery::mock( \NewfoldLabs\WP\Module\Data\HiiveConnection::class ); + + $hiive_connection->shouldReceive('verify_token') + ->with('an-invalid-token') + ->andReturnFalse(); + + $sut = new Verify( $hiive_connection ); + + $request = new \WP_REST_Request(); + $request->set_param( 'token', 'an-invalid-token' ); + + $result = $sut->get_items( $request ); + + self::assertEquals(401, $result->status); + } +}