Skip to content

Commit

Permalink
Ensure block comments are always sanitized on save and render (#35246)
Browse files Browse the repository at this point in the history
  • Loading branch information
heavyweight authored Feb 8, 2024
1 parent ea54fd3 commit cd15410
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: security


Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php
/**
* Verbum Block Utils
*
* @package automattic/jetpack-mu-plugins
*/

/**
* Verbum_Block_Utils offer utility functions for sanitizing and parsing blocks.
*/
class Verbum_Block_Utils {
/**
* Remove blocks that aren't allowed
*
* @param string $content - Text of the comment.
* @return string
*/
public static function remove_blocks( $content ) {
if ( ! has_blocks( $content ) ) {
return $content;
}

$allowed_blocks = self::get_allowed_blocks();
// The block attributes come slashed and `parse_blocks` won't be able to parse them.
$content = wp_unslash( $content );
$blocks = parse_blocks( $content );
$output = '';

foreach ( $blocks as $block ) {
if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
$output .= serialize_block( $block );
}
}

return ltrim( $output );
}

/**
* Filter blocks from content according to our allowed blocks
*
* @param string $content - The content to be processed.
* @return array
*/
private static function filter_blocks( $content ) {
$registry = new WP_Block_Type_Registry();
$allowed_blocks = self::get_allowed_blocks();

foreach ( $allowed_blocks as $allowed_block ) {
$registry->register( $allowed_block );
}

$filtered_blocks = array();
$blocks = parse_blocks( $content );

foreach ( $blocks as $block ) {
$filtered_blocks[] = new WP_Block( $block, array(), $registry );
}

return $filtered_blocks;
}

/**
* Render blocks in the comment content
* Filters blocks that aren't allowed
*
* @param string $comment_content - Text of the comment.
* @return string
*/
public static function render_verbum_blocks( $comment_content ) {
if ( ! has_blocks( $comment_content ) ) {
return $comment_content;
}

$blocks = self::filter_blocks( $comment_content );
$comment_content = '';

foreach ( $blocks as $block ) {
$comment_content .= $block->render();
}

return $comment_content;
}

/**
* Get a list of allowed blocks by looking at the allowed comment tags
*
* @return string[]
*/
public static function get_allowed_blocks() {
global $allowedtags;

$allowed_blocks = array( 'core/paragraph', 'core/list', 'core/code', 'core/list-item', 'core/quote', 'core/image', 'core/embed' );
$convert = array(
'blockquote' => 'core/quote',
'h1' => 'core/heading',
'h2' => 'core/heading',
'h3' => 'core/heading',
'img' => 'core/image',
'ul' => 'core/list',
'ol' => 'core/list',
'pre' => 'core/code',
);

foreach ( array_keys( $allowedtags ) as $tag ) {
if ( isset( $convert[ $tag ] ) ) {
$allowed_blocks[] = $convert[ $tag ];
}
}

return $allowed_blocks;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

declare( strict_types = 1 );

require_once __DIR__ . '/class-verbum-block-utils.php';

/**
* Verbum_Gutenberg_Editor is responsible for loading the Gutenberg editor for comments.
*
Expand All @@ -29,10 +31,11 @@ function () {
},
9999
);

add_filter( 'init', array( $this, 'remove_strict_kses_filters' ) );
add_filter( 'comment_text', array( $this, 'render_verbum_blocks' ) );
add_filter( 'pre_comment_content', array( $this, 'remove_blocks' ) );
add_filter( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_filter( 'comment_text', array( \Verbum_Block_Utils::class, 'render_verbum_blocks' ) );
add_filter( 'pre_comment_content', array( \Verbum_Block_Utils::class, 'remove_blocks' ) );
}

/**
Expand All @@ -59,84 +62,4 @@ public function enqueue_assets() {
$vbe_cache_buster
);
}

/**
* Render blocks in the comment content
* Filters blocks that aren't allowed
*
* @param string $comment_content - Text of the comment.
* @return string
*/
public function render_verbum_blocks( $comment_content ) {
if ( ! has_blocks( $comment_content ) ) {
return $comment_content;
}

$blocks = parse_blocks( $comment_content );
$comment_content = '';

$allowed_blocks = self::get_allowed_blocks();
foreach ( $blocks as $block ) {
if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
$comment_content .= render_block( $block );
}
}

return $comment_content;
}

/**
* Remove blocks that aren't allowed
*
* @param string $content - Text of the comment.
* @return string
*/
public function remove_blocks( $content ) {
if ( ! has_blocks( $content ) ) {
return $content;
}

$allowed_blocks = self::get_allowed_blocks();
// The block attributes come slashed and `parse_blocks` won't be able to parse them.
$content = wp_unslash( $content );
$blocks = parse_blocks( $content );
$output = '';

foreach ( $blocks as $block ) {
if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
$output .= serialize_block( $block );
}
}

return ltrim( $output );
}

/**
* Get a list of allowed blocks by looking at the allowed comment tags
*
* @return string[]
*/
public static function get_allowed_blocks() {
global $allowedtags;

$allowed_blocks = array( 'core/paragraph', 'core/list', 'core/code', 'core/list-item', 'core/quote', 'core/image', 'core/embed' );
$convert = array(
'blockquote' => 'core/quote',
'h1' => 'core/heading',
'h2' => 'core/heading',
'h3' => 'core/heading',
'img' => 'core/image',
'ul' => 'core/list',
'ol' => 'core/list',
'pre' => 'core/code',
);

foreach ( array_keys( $allowedtags ) as $tag ) {
if ( isset( $convert[ $tag ] ) ) {
$allowed_blocks[] = $convert[ $tag ];
}
}

return $allowed_blocks;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
require_once __DIR__ . '/assets/class-wpcom-rest-api-v2-verbum-auth.php';
require_once __DIR__ . '/assets/class-wpcom-rest-api-v2-verbum-oembed.php';
require_once __DIR__ . '/assets/class-verbum-gutenberg-editor.php';
require_once __DIR__ . '/assets/class-verbum-block-utils.php';

/**
* Verbum Comments Experience
Expand Down Expand Up @@ -247,7 +248,7 @@ public function enqueue_assets() {
'enableSubscriptionModal' => boolval( $this->should_show_subscription_modal() ),
'currentLocale' => $locale,
'isJetpackComments' => is_jetpack_comments(),
'allowedBlocks' => \Verbum_Gutenberg_Editor::get_allowed_blocks(),
'allowedBlocks' => \Verbum_Block_Utils::get_allowed_blocks(),
'embedNonce' => wp_create_nonce( 'embed_nonce' ),
'verbumBundleUrl' => plugins_url( 'dist/index.js', __FILE__ ),
'isRTL' => is_rtl( $locale ),
Expand Down Expand Up @@ -560,9 +561,9 @@ public function should_load_gutenberg_comments() {
return false;
}

$blog_id = verbum_get_blog_id();
$e2e_tests = has_blog_sticker( 'a8c-e2e-test-blog', $blog_id );
$has_blocks_flag = has_blog_sticker( 'verbum-block-comments', $blog_id );
$blog_id = $this->blog_id;
$e2e_tests = function_exists( 'has_blog_sticker' ) && has_blog_sticker( 'a8c-e2e-test-blog', $blog_id );
$has_blocks_flag = function_exists( 'has_blog_sticker' ) && has_blog_sticker( 'verbum-block-comments', $blog_id );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$gutenberg_query_param = isset( $_GET['verbum_gutenberg'] ) ? intval( $_GET['verbum_gutenberg'] ) : null;
// This will release to 50% of sites.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
/**
* Test class for Verbum_Block_Utils.
*
* @package automattic/jetpack-mu-wpcom
*/

use Automattic\Jetpack\Jetpack_Mu_Wpcom;
require_once Jetpack_Mu_Wpcom::PKG_DIR . 'src/features/verbum-comments/assets/class-verbum-block-utils.php';

/**
* Test class for Verbum_Block_Utils.
*
* @coversDefaultClass Verbum_Block_Utils
*/
class Verbum_Block_Utils_Test extends \WorDBless\BaseTestCase {
/**
* Ensure string comments are not modified when 'render_verbum_blocks' is applied
*
* @covers Verbum_Block_Utils::render_verbum_blocks
*/
public function test_comment_text_string_comment() {
$comment_content = 'This is a test comment';
$filtered_content = Verbum_Block_Utils::render_verbum_blocks( $comment_content );
$this->assertEquals( $comment_content, $filtered_content );
}

/**
* Ensure blocks are filtered when 'render_verbum_blocks' is applied
*
* @covers Verbum_Block_Utils::render_verbum_blocks
*/
public function test_comment_text_block_sanitization() {
$comment_content = '<!-- wp:paragraph -->Testing<!-- /wp:paragraph --><!-- wp:latest-posts -->';
$filtered_content = Verbum_Block_Utils::render_verbum_blocks( $comment_content );
$this->assertEquals( 'Testing', $filtered_content );
}

/**
* Ensure blocks are rendered properly
*
* @covers Verbum_Block_Utils::render_verbum_blocks
*/
public function test_comment_text_block_sanitization_sanity_check() {
$comment_content = '<!-- wp:paragraph --><p>test</p><!-- /wp:paragraph --><!-- wp:list --><ul><!-- wp:list-item --><li>1</li><!-- /wp:list-item --><!-- wp:list-item --><li>2</li><!-- /wp:list-item --><!-- wp:list-item --><li>3</li><!-- /wp:list-item --></ul><!-- /wp:list --><!-- wp:quote --><blockquote class="wp-block-quote"><!-- wp:paragraph --><p>something</p><!-- /wp:paragraph --><cite>someone</cite></blockquote><!-- /wp:quote -->';
$filtered_content = preg_replace( '/\R+/', '', Verbum_Block_Utils::render_verbum_blocks( $comment_content ) );

$expected_content = '<p>test</p><ul><li>1</li><li>2</li><li>3</li></ul><blockquote class="wp-block-quote"><p>something</p><cite>someone</cite></blockquote>';
$this->assertEquals( $expected_content, $filtered_content );
}

/**
* Ensure innerBlocks are filtered when 'render_verbum_blocks' is applied
*
* @covers Verbum_Block_Utils::render_verbum_blocks
*/
public function test_comment_text_block_sanitization_inner_blocks() {
$comment_content = '<!-- wp:paragraph {} --><!-- wp:latest-posts --><!-- /wp:paragraph -->';
$filtered_content = Verbum_Block_Utils::render_verbum_blocks( $comment_content );
$this->assertSame( '', $filtered_content );
}

/**
* Ensure string comments are not modified when 'pre_comment_content' is applied
*
* @covers Verbum_Block_Utils::remove_blocks
*/
public function test_pre_comment_content_string_comment() {
$comment_content = 'This is a test comment';
$filtered_content = Verbum_Block_Utils::remove_blocks( $comment_content );
$this->assertEquals( $comment_content, $filtered_content );
}

/**
* Ensure blocks are filtered when 'pre_comment_content' is applied
*
* @covers Verbum_Block_Utils::remove_blocks
*/
public function test_pre_comment_content__block_sanitization() {
$comment_content = '<!-- wp:paragraph -->Testing<!-- /wp:paragraph --><!-- wp:latest-posts -->';
$filtered_content = Verbum_Block_Utils::remove_blocks( $comment_content );
$this->assertEquals( '<!-- wp:paragraph -->Testing<!-- /wp:paragraph -->', $filtered_content );
}
}

0 comments on commit cd15410

Please sign in to comment.