Skip to content

Commit

Permalink
Issue #843: Initial registration of the REST endpoint for validation.
Browse files Browse the repository at this point in the history
Per Weston's description in PR #912,
It allows sending a POST with markup for validation.
The headers should have 'Content-Type' of 'application/json.'
And it should pass the markup in the param 'markup.'
The current response only has 'is_error.'
@todo: look at returning more in the response,
like the stripped tags and attributes.
Also, add nonce verification.
  • Loading branch information
Ryan Kienstra committed Jan 29, 2018
1 parent 4714062 commit e209005
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 18 deletions.
1 change: 1 addition & 0 deletions amp.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ function amp_after_setup_theme() {
}

add_action( 'init', 'amp_init' );
add_action( 'rest_api_init', 'AMP_Mutation_Utils::amp_rest_validation' );
add_action( 'widgets_init', 'AMP_Theme_Support::register_widgets' );
add_action( 'admin_init', 'AMP_Options_Manager::register_settings' );
add_filter( 'amp_post_template_analytics', 'amp_add_custom_analytics' );
Expand Down
84 changes: 84 additions & 0 deletions includes/utils/class-amp-mutation-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class AMP_Mutation_Utils {
*/
const NODE_REMOVED = 'removed';

/**
* Key for the markup value in the REST API endpoint.
*
* @var string.
*/
const MARKUP_KEY = 'markup';

/**
* The attributes that the sanitizer removed.
*
Expand Down Expand Up @@ -84,4 +91,81 @@ public static function process_markup( $markup ) {
AMP_Content_Sanitizer::sanitize( $markup, amp_get_content_sanitizers(), $args );
}

/**
* Registers the REST API endpoint for validation.
*
* @return void.
*/
public static function amp_rest_validation() {
register_rest_route( 'amp-wp/v1', '/validate', array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'validate_markup' ),
'args' => array(
self::MARKUP_KEY => array(
'validate_callback' => array( __CLASS__, 'validate_arg' ),
),
),
'permission_callback' => array( __CLASS__, 'permission' ),
) );
}

/**
* The permission callback for the REST request.
*
* @return boolean $has_permission Whether the current user has the permission.
*/
public static function permission() {
return current_user_can( 'edit_posts' );
}

/**
* Validate the markup passed to the REST API.
*
* @param WP_REST_Request $request The REST request.
* @return array|WP_Error.
*/
public static function validate_markup( WP_REST_Request $request ) {
$json = $request->get_json_params();
if ( empty( $json[ self::MARKUP_KEY ] ) ) {
return new WP_Error( 'no_markup', 'No markup passed to validator', array(
'status' => 404,
) );
}

self::process_markup( $json[ self::MARKUP_KEY ] );
$response = array(
'is_error' => self::was_node_removed(),
);
self::reset_removed();

return $response;
}

/**
* Reset the stored removed nodes and attributes.
*
* After testing if the markup is valid,
* these static values will remain.
* So reset them in case another test is needed.
*
* @return void.
*/
public static function reset_removed() {
self::$removed_nodes = null;
self::$removed_attributes = null;
}

/**
* Validate the argument in the REST API request.
*
* It would be ideal to simply pass 'is_string' in register_rest_route().
* But it always returned false.
*
* @param mixed $arg The argument to validate.
* @return boolean $is_valid Whether the argument is valid.
*/
public static function validate_arg( $arg ) {
return is_string( $arg );
}

}
142 changes: 124 additions & 18 deletions tests/test-class-amp-mutation-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ class Test_AMP_Mutation_Utils extends \WP_UnitTestCase {
*/
public $node;

/**
* The expected REST API route.
*
* @var string
*/
public $expected_route = '/amp-wp/v1/validate';

/**
* A tag that the sanitizer should strip.
*
* @var string
*/
public $disallowed_tag = '<script async src="https://example.com/script"></script>'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript

/**
* A valid image that sanitizers should not alter.
*
* @var string
*/
public $valid_amp_img = '<amp-img id="img-123" media="(min-width: 600x)" src="/assets/example.jpg" width=200 height=500 layout="responsive"></amp-img>';

/**
* The name of the tag to test.
*
Expand All @@ -33,9 +54,9 @@ class Test_AMP_Mutation_Utils extends \WP_UnitTestCase {
*/
public function setUp() {
parent::setUp();
$dom_document = new DOMDocument( '1.0', 'utf-8' );
$this->node = $dom_document->createElement( self::TAG_NAME );
$this->reset_removed();
$dom_document = new DOMDocument( '1.0', 'utf-8' );
$this->node = $dom_document->createElement( self::TAG_NAME );
AMP_Mutation_Utils::reset_removed();
}

/**
Expand Down Expand Up @@ -76,27 +97,27 @@ public function test_was_node_removed() {
* @see AMP_Mutation_Utils::process_markup()
*/
public function test_process_markup() {
$valid_amp_img = '<amp-img id="img-123" media="(min-width: 600x)" src="/assets/example.jpg" width=200 height=500 layout="responsive"></amp-img>';
AMP_Mutation_Utils::process_markup( $valid_amp_img );

AMP_Mutation_Utils::process_markup( $this->valid_amp_img );
$this->assertEquals( null, AMP_Mutation_Utils::$removed_nodes );
$this->assertEquals( null, AMP_Mutation_Utils::$removed_attributes );

$this->reset_removed();
AMP_Mutation_Utils::reset_removed();
$video = '<video src="https://example.com/video">';
AMP_Mutation_Utils::process_markup( $valid_amp_img );
AMP_Mutation_Utils::process_markup( $this->valid_amp_img );
// This isn't valid AMP, but the sanitizer should convert it to an <amp-video>, without stripping anything.
$this->assertEquals( null, AMP_Mutation_Utils::$removed_nodes );
$this->assertEquals( null, AMP_Mutation_Utils::$removed_attributes );

$this->reset_removed();
$disallowed_script = '<script async src="https://example.com/script"></script>'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
AMP_Mutation_Utils::process_markup( $disallowed_script );
AMP_Mutation_Utils::reset_removed();

AMP_Mutation_Utils::process_markup( $this->disallowed_tag );
$removed_node = reset( AMP_Mutation_Utils::$removed_nodes );
$this->assertEquals( 'script', $removed_node->nodeName );
$this->assertEquals( null, AMP_Mutation_Utils::$removed_attributes );

$this->reset_removed();
$disallowed_style = '<div style="display:none"></div>';
AMP_Mutation_Utils::reset_removed();
$disallowed_style = '<div style="display:none"></div>';
AMP_Mutation_Utils::process_markup( $disallowed_style );
$removed_attribute = reset( AMP_Mutation_Utils::$removed_attributes );
$expected_removed_attributes = array(
Expand All @@ -105,7 +126,7 @@ public function test_process_markup() {
$this->assertEquals( null, AMP_Mutation_Utils::$removed_nodes );
$this->assertEquals( $expected_removed_attributes, $removed_attribute );

$this->reset_removed();
AMP_Mutation_Utils::reset_removed();
$invalid_video = '<video width="200" height="100"></video>';
AMP_Mutation_Utils::process_markup( $invalid_video );
$removed_node = reset( AMP_Mutation_Utils::$removed_nodes );
Expand All @@ -114,13 +135,98 @@ public function test_process_markup() {
}

/**
* Set the removed nodes and attributes to null.
* Test amp_rest_validation.
*
* @see AMP_Mutation_Utils::amp_rest_validation()
*/
public function test_amp_rest_validation() {
$routes = rest_get_server()->get_routes();
$route = $routes[ $this->expected_route ][0];
$methods = array(
'POST' => true,
);
$args = array(
'markup' => array(
'validate_callback' => array( 'AMP_Mutation_Utils', 'validate_arg' ),
),
);

$this->assertEquals( $args, $route['args'] );
$this->assertEquals( array( 'AMP_Mutation_Utils', 'validate_markup' ), $route['callback'] );
$this->assertEquals( $methods, $route['methods'] );
$this->assertEquals( array( 'AMP_Mutation_Utils', 'permission' ), $route['permission_callback'] );
}

/**
* Test permission.
*
* @see AMP_Mutation_Utils::permission()
*/
public function test_permission() {
wp_set_current_user( $this->factory()->user->create( array(
'role' => 'subscriber',
) ) );
$this->assertFalse( AMP_Mutation_Utils::permission() );

wp_set_current_user( $this->factory()->user->create( array(
'role' => 'administrator',
) ) );
$this->assertTrue( AMP_Mutation_Utils::permission() );
}

/**
* Test validate_markup.
*
* @see AMP_Mutation_Utils::validate_markup()
*/
public function test_validate_markup() {
$request = new WP_REST_Request( 'POST', $this->expected_route );
$request->set_header( 'content-type', 'application/json' );
$request->set_body( wp_json_encode( array(
AMP_Mutation_Utils::MARKUP_KEY => $this->disallowed_tag,
) ) );
$response = AMP_Mutation_Utils::validate_markup( $request );
$expected_response = array(
'is_error' => true,
);
$this->assertEquals( $expected_response, $response );

$request->set_body( wp_json_encode( array(
AMP_Mutation_Utils::MARKUP_KEY => $this->valid_amp_img,
) ) );
$response = AMP_Mutation_Utils::validate_markup( $request );
$expected_response = array(
'is_error' => false,
);
$this->assertEquals( $expected_response, $response );
}

/**
* Test reset_removed
*
* @see AMP_Mutation_Utils::reset_removed()
*/
public function test_reset_removed() {
AMP_Mutation_Utils::$removed_nodes[] = $this->node;
AMP_Mutation_Utils::$removed_attributes = array( 'onclick' );
AMP_Mutation_Utils::reset_removed();

$this->assertEquals( null, AMP_Mutation_Utils::$removed_nodes );
$this->assertEquals( null, AMP_Mutation_Utils::$removed_attributes );
}

/**
* Test validate_arg
*
* @return void.
* @see AMP_Mutation_Utils::validate_arg()
*/
public function reset_removed() {
AMP_Mutation_Utils::$removed_nodes = null;
AMP_Mutation_Utils::$removed_attributes = null;
public function test_validate_arg() {
$invalid_number = 54321;
$invalid_array = array( 'foo', 'bar' );
$valid_string = '<div class="baz"></div>';
$this->assertFalse( AMP_Mutation_Utils::validate_arg( $invalid_number ) );
$this->assertFalse( AMP_Mutation_Utils::validate_arg( $invalid_array ) );
$this->assertTrue( AMP_Mutation_Utils::validate_arg( $valid_string ) );
}

}

0 comments on commit e209005

Please sign in to comment.