Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bulk scan/fix for insecure content from the admin #112

Merged
merged 9 commits into from
Aug 8, 2023

Conversation

kmgalanakis
Copy link
Contributor

@kmgalanakis kmgalanakis commented Mar 23, 2023

Description of the Change

Closes #58

How to test the Change

  • Create a few posts with the following WP cli command echo '<!-- wp:image {"sizeSlug":"large"} --><figure class="wp-block-image size-large"><img src="http://cdn.pixabay.com/photo/2015/06/19/21/24/avenue-815297_960_720.jpg" alt=""/></figure><!-- /wp:image --><!-- wp:image {"sizeSlug":"large"} --><figure class="wp-block-image size-large"><img src="http://cdn.pixabay.com/photo/2015/06/19/21/24/avenue-815297_960_720.jpg" alt=""/></figure><!-- /wp:image --><!-- wp:image {"sizeSlug":"large"} --><figure class="wp-block-image size-large"><img src="http://cdn.pixabay.com/photo/2015/06/19/21/24/avenue-815297_960_720.jpg" alt=""/></figure><!-- /wp:image -->' | wp post generate --count=1 --post_type=car
  • The newly created posts will contain insecure content (image). In the Admin, go to Tools -> Insecure Content Warning and run (and dry-run) the process to fix the newly created posts. Play with all the options.
  • Verify the the already existing WP cli command to fix insecure content in posts still works.

Changelog Entry

Added - New admin screen to fix insecure content in posts.

Checklist:

  • I agree to follow this project's Code of Conduct.
  • I have updated the documentation accordingly.
  • I have added tests to cover my change.
  • All new and existing tests pass.

@kmgalanakis kmgalanakis self-assigned this Mar 23, 2023
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch 2 times, most recently from 41967ab to 292e8c9 Compare March 24, 2023 08:45
@kmgalanakis kmgalanakis changed the title Initial commit Bulk scan/fix for insecure content from the admin Mar 24, 2023
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch from 292e8c9 to 853d690 Compare March 24, 2023 19:36
@jeffpaul jeffpaul added this to the 1.1.0 milestone Apr 10, 2023
@jeffpaul jeffpaul requested review from a team and peterwilsoncc and removed request for a team April 10, 2023 21:44
@peterwilsoncc
Copy link
Contributor

@kmgalanakis Feel free to ping me on the ticket once this is ready for review so it doesn't get lost.

I'm happy to do an early scan if you wish, too, just say the word.

@kmgalanakis
Copy link
Contributor Author

@kmgalanakis Feel free to ping me on the ticket once this is ready for review so it doesn't get lost.

I'm happy to do an early scan if you wish, too, just say the word.

@peterwilsoncc the biggest part of the work here is done but the problem is that I realized my current approach is not performing very well when there is a large number of posts with insecure content to be updated. I want to change that and as soon as it's ready I will let you know.

@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch 3 times, most recently from d13b79a to 8837f00 Compare April 17, 2023 21:31
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch 13 times, most recently from 268e858 to c0be893 Compare April 28, 2023 18:36
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch from c0be893 to 6cba4df Compare April 28, 2023 18:41
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch from 6cba4df to 50b2b85 Compare April 28, 2023 21:49
@kmgalanakis kmgalanakis marked this pull request as ready for review April 28, 2023 21:51
@kmgalanakis kmgalanakis requested a review from a team as a code owner April 28, 2023 21:51
@kmgalanakis
Copy link
Contributor Author

@kmgalanakis Feel free to ping me on the ticket once this is ready for review so it doesn't get lost.

I'm happy to do an early scan if you wish, too, just say the word.

@peterwilsoncc the fix is ready for review. I can still see the E2E tests failing but they are also failing on develop and I don't know how to fix them. I would be more than happy to also add the E2E test for this new feature but I don't know when I will be able to focus on this again, so I'm ok to do it after this feature is merged.

Please let me know what you think about the approach and the code.

Thank you.

Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some notes inline, there's a couple of security pick ups in there that are blockers.

There's a lot of unrelated changes in this PR due to the eslint fixes, while that should probably be done, I'd rather do it on a separate ticket to avoid confiscation. Especially given the introduction of bulk edits/rest endpoints that need a good security review.

$hook = add_management_page(
__( 'Insecure Content Warning Admin', 'insecure-content-warning' ),
__( 'Insecure Content Warning', 'insecure-content-warning' ),
'install_plugins',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'install_plugins',
'edit_posts',

As a general capability, this seems closes to what the page is doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thank you!

Comment on lines 150 to 151
// Wait for a while before fixing the rest.
usleep( 1000 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the wait, what is the intent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember exactly because it's been a while but I'm guessing this was a leftover from my initial approach. I removed it.


foreach ( $this->warning_count as $warning ) {
$warning_message = array_shift( $warning );
$output .= '<span class="warning">' . __( 'Warning', 'insecure-content-warning' ) . '</span>: ' . $warning_message . PHP_EOL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$output .= '<span class="warning">' . __( 'Warning', 'insecure-content-warning' ) . '</span>: ' . $warning_message . PHP_EOL;
$output .= '<span class="warning">' . __( 'Warning', 'insecure-content-warning' ) . '</span>: ' . wp_strip_all_tags( $warning_message ) . PHP_EOL;

I know it isn't needed now but it protects ourselves from our future selves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better safe than sorry, thank you.

Comment on lines 194 to 195
// translators: Message to show when the system is unable to fetch details for the post.
$message = sprintf( __( 'Unable to fetch details for post %s', 'insecure-content-warning' ), $post_id );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translator notes are intended to describe the placeholder rather than the string.

Suggested change
// translators: Message to show when the system is unable to fetch details for the post.
$message = sprintf( __( 'Unable to fetch details for post %s', 'insecure-content-warning' ), $post_id );
// translators: %d The Post ID being scanned for insecure content.
$message = sprintf( __( 'Unable to fetch details for post %d', 'insecure-content-warning' ), $post_id );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thank you.

// translators: Message to show when the system is unable to fetch details for the post.
$message = sprintf( __( 'Unable to fetch details for post %s', 'insecure-content-warning' ), $post_id );
if ( defined( 'WP_CLI' ) && WP_CLI ) {
// translators: Message to show when the system is unable to fetch details for the post.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// translators: Message to show when the system is unable to fetch details for the post.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thank you.

}
$this->fixed_post_count[] = array( 'URL(s) fixed summary' => $dry_run_message );
} else {
// translators: Summary to show on fix.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thank you.

Comment on lines 32 to 34
<option selected="selected" value="<?php echo esc_attr( 'all' ); ?>"><?php esc_html_e( 'All', 'insecure-content-warning' ); ?></option>
<option value="<?php echo esc_attr( 'posts' ); ?>"><?php esc_html_e( 'Individual post(s)', 'insecure-content-warning' ); ?></option>
<option value="<?php echo esc_attr( 'all_from_post_type' ); ?>"><?php esc_html_e( 'All from post type', 'insecure-content-warning' ); ?></option>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to escape the constants, just variables and translations/

Suggested change
<option selected="selected" value="<?php echo esc_attr( 'all' ); ?>"><?php esc_html_e( 'All', 'insecure-content-warning' ); ?></option>
<option value="<?php echo esc_attr( 'posts' ); ?>"><?php esc_html_e( 'Individual post(s)', 'insecure-content-warning' ); ?></option>
<option value="<?php echo esc_attr( 'all_from_post_type' ); ?>"><?php esc_html_e( 'All from post type', 'insecure-content-warning' ); ?></option>
<option selected="selected" value="all"><?php esc_html_e( 'All', 'insecure-content-warning' ); ?></option>
<option value="posts"><?php esc_html_e( 'Individual post(s)', 'insecure-content-warning' ); ?></option>
<option value="all_from_post_type"><?php esc_html_e( 'All from post type', 'insecure-content-warning' ); ?></option>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thank you.

array(
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\\fix_endpoint',
'permission_callback' => '__return_true',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'permission_callback' => '__return_true',
'permission_callback' => 'edit_posts',

This might end up needing to be a little more complex to allow for CPTs registered with custom capabilities.

As it's editing the posts, it will definitely need to include a capability check and should probably include a nonce check for users without an application password.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I take the time to research this now?

array(
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\\count_for_fix_endpoint',
'permission_callback' => '__return_true',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'permission_callback' => '__return_true',
'permission_callback' => 'edit_posts',

Reveals data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thank you.

*
* @return array
*/
function prepare_fix_params( WP_REST_Request $request ): array {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using the REST APIs schema for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does nothing more than prepare the params coming from the request for the methods that implement that counting and the fixing of posts with insecure content. How do you think we could use the REST APIs schema for this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, the goal is to do it the WordPress way -- using schema gets a lot of things for free.

Setting the parameters in the schema will use the validation and sanitization built in to WP, so specifying the expected types there will automatically throw warnings and validation errors for invalid values.

If WP discovers a security error in the RES API, by using the schema we can ensure that the upstream fixes are applied to the endpoints here too.

For post types, you'll probably need to do a little more validation -- see this line onwards in distributor -- to make sure the post type is intended to be visible in the rest api, the user has access, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functionality to bulk scan/fix insecure content from the admin is actually a replication of the WP CLI functionality. The WP CLI functionality can properly scan/fix post types that are not shown in REST, so in my opinion, the admin functionality should behave exactly the same. According to my tests, neither the WP CLI nor the built admin functionality has problems scanning/fixing posts from a post type that is not shown in REST.

What I don't understand from your comment is how this specific function here could be avoided by the use of the WP REST API. Let me explain once again here that what this method does is "transform" the parameters of the WP REST API endpoint call into suitable parameters for the methods that count/scan/fix posts with insecure content. You see, these methods are also used from the WP CLI command and it wouldn't make too much sense to have a 1:1 relation between the REST API endpoints parameters with the parameters fo those methods, that is why I'm using this "transformation" method.

Still, I took the time to implement some more validation and sanitization callbacks for the actual REST API endpoint parameters to play more on the safe side.

*
* @return array
*/
function prepare_fix_params( WP_REST_Request $request ): array {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, the goal is to do it the WordPress way -- using schema gets a lot of things for free.

Setting the parameters in the schema will use the validation and sanitization built in to WP, so specifying the expected types there will automatically throw warnings and validation errors for invalid values.

If WP discovers a security error in the RES API, by using the schema we can ensure that the upstream fixes are applied to the endpoints here too.

For post types, you'll probably need to do a little more validation -- see this line onwards in distributor -- to make sure the post type is intended to be visible in the rest api, the user has access, etc.

@@ -2,27 +2,26 @@ import blurInsecure from './utils/blur-insecure';
import checkContent from './utils/check-content';
import findElements from './utils/find-elements';
import replaceContent from './utils/replace';
import $ from 'jquery';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the start of the file you'll need to add:

// eslint-disable-next-line import/no-unresolved
import { jQuery } from 'jquery';

This will ensure the dependency is included in dist/js/classic-editor.asset.php by @wordpress/dependency-extraction-webpack-plugin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean import jQuery from 'jquery';. Initially, this didn't play nicely with ESLint but I ended up copying the way it's done in Distributor and now it's fine.

Thank you.

import { debounce } from 'underscore';
import { getScrollContainer } from '@wordpress/dom';
import apiRequest from '@wordpress/api-request';
import domReady from '@wordpress/dom-ready';
import { dispatch, select, subscribe } from '@wordpress/data';
import $ from 'jquery';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thank you.

@@ -1,23 +1,21 @@
import $ from 'jquery';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thank you.

import blurInsecure from './blur-insecure';
import { scanElements } from './scan-elements';

import { __, _nx, sprintf } from '@wordpress/i18n';
import $ from 'jquery';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thank you.

@@ -1,29 +1,27 @@
import { get } from 'underscore';

import $ from 'jquery';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thank you.

@@ -1,5 +1,3 @@
import $ from 'jquery';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thank you.

public function count_post_to_check( string $post_type, int $posts_per_page, int $post_offset ): int {
$args = array(
'posts_per_page' => $posts_per_page,
'post_type' => 'all' === $post_type ? 'any' : $post_type,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be limited to only posts available via the rest api -- per note re:schema. Many e-commerce plugins use post types for orders sow we definitely need to exclude those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I commented on the other conversation, the functionality to bulk scan/fix insecure content from the admin is actually a replication of the WP CLI functionality. The WP CLI functionality can properly scan/fix post types that are not shown in REST, so in my opinion, the admin functionality should behave exactly the same. According to my tests, neither the WP CLI nor the built admin functionality has problems scanning/fixing posts from a post type that is not shown in REST.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WP CLI functionality can properly scan/fix post types that are not shown in REST, so in my opinion, the admin functionality should behave exactly the same.

The difference between the two is that the WP-CLI is basically the equivalent of running as the root user on a server. Once someone has access to the server they can do anything, such as running wp db clean --yes so permissions don't apply.

For users with access to the dashboard only, we need to check that they have full permission to make any changes to a post. If a post type isn't editable via the rest API then we need to assume the developer has reasons for preventing this and respect their decision

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you say makes perfect sense @peterwilsoncc. I've modified the method that counts posts with and the method that fixes insecure content so that posts from post types that are not shown in RFEST are excluded from the process.

Please let me know if this is what you meant. Thank you for your feedback and patience.

// Check if a https version of the URL exists.
$secure_version_exists = false;
$ssl = preg_replace( '/^http:/i', 'https:', $url );
$response = wp_remote_get( $ssl );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe, maybe, maybe???

Per MDN, this should tell you whether the file exists without having to download the full content. It should return the HTTP headers as-though you were making a GET request.

Suggested change
$response = wp_remote_get( $ssl );
$response = wp_remote_head( $ssl );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the code from the implementation of the WP CLI command but I can confirm that your point is right, it checks if the URL resolves to an existing resource without downloading it just by checking the returned HTTP header.

Good point, thank you.

…EST API endpoint args with validation and sanitization callbacks and fix an issue with the WP CLI command generating a PHP fatal error.
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch from 7226db7 to df6ab38 Compare June 16, 2023 11:15
@dkotter dkotter modified the milestones: 1.1.0, 1.2.0 Jun 16, 2023
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch from a33d13a to df6ab38 Compare August 2, 2023 06:28
@kmgalanakis kmgalanakis force-pushed the feature/bulk-scan-fix-from-admin branch from 58a0cdd to d2b6321 Compare August 2, 2023 07:09
Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good with a minor nitpcik.

public function get_wp_query_post_type_args(): array {
return array(
'show_ui' => true,
'public' => true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'public' => true,

I think the other two will be fine, the user may have permission to edit some private post types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

$total = 0;

// Exclude post from post types not shown in REST when fixing insecure content from the admin UI.
if ( ! defined( 'WP_CLI' ) && 'any' === $post_type ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you've used all elsewhere and any here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixed.

peterwilsoncc
peterwilsoncc previously approved these changes Aug 7, 2023
Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolving merge conflicts dismissed my review, reapproving.

@jeffpaul jeffpaul merged commit 494aa7d into develop Aug 8, 2023
5 of 6 checks passed
@jeffpaul jeffpaul deleted the feature/bulk-scan-fix-from-admin branch August 8, 2023 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add ability to run a bulk scan/fix from the admin
4 participants