diff --git a/projects/packages/waf/src/class-rest-controller.php b/projects/packages/waf/src/class-rest-controller.php index 6e7747e350a3c..92e144b25bd1f 100644 --- a/projects/packages/waf/src/class-rest-controller.php +++ b/projects/packages/waf/src/class-rest-controller.php @@ -109,88 +109,124 @@ public static function waf() { * @return WP_REST_Response|WP_Error */ public static function update_waf( $request ) { - // Automatic Rules Enabled - if ( isset( $request[ Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME ] ) ) { - update_option( Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME, $request->get_param( Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME ) ? '1' : '' ); - } - /** - * IP Lists Enabled - * - * @deprecated 0.17.0 This is a legacy option maintained here for backwards compatibility. - */ - if ( isset( $request['jetpack_waf_ip_list'] ) ) { - update_option( Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME, $request['jetpack_waf_ip_list'] ? '1' : '' ); - update_option( Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME, $request['jetpack_waf_ip_list'] ? '1' : '' ); - } + // return new WP_Error( + // 'test_waf_test', + // 'test', + // array( 'status' => 500 ) + // ); - // IP Block List - if ( isset( $request[ Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME ] ) ) { - update_option( Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME, $request[ Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME ] ); - } - if ( isset( $request[ Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME ] ) ) { - update_option( Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME, $request[ Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME ] ? '1' : '' ); + if ( ! defined( 'WP_DEBUG' ) ) { + define( 'WP_DEBUG', true ); } - - // IP Allow List - if ( isset( $request[ Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ] ) ) { - update_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME, $request[ Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ] ); + if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) { + define( 'WP_DEBUG_DISPLAY', true ); } - if ( isset( $request[ Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME ] ) ) { - update_option( Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME, $request[ Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME ] ? '1' : '' ); + if ( ! defined( 'WP_DEBUG_LOG' ) ) { + define( 'WP_DEBUG_LOG', true ); } - // Share Data - if ( isset( $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ) ) { - // If a user disabled the regular share we should disable the debug share data option. - if ( ! $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ) { - update_option( Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME, '' ); + try { + + // Automatic Rules Enabled + if ( isset( $request[ Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME ] ) ) { + update_option( Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME, $request->get_param( Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME ) ? '1' : '' ); } - update_option( Waf_Runner::SHARE_DATA_OPTION_NAME, $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ? '1' : '' ); - } + /** + * IP Lists Enabled + * + * @deprecated 0.17.0 This is a legacy option maintained here for backwards compatibility. + */ + if ( isset( $request['jetpack_waf_ip_list'] ) ) { + update_option( Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME, $request['jetpack_waf_ip_list'] ? '1' : '' ); + update_option( Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME, $request['jetpack_waf_ip_list'] ? '1' : '' ); + } - // Share Debug Data - if ( isset( $request[ Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME ] ) ) { - // If a user toggles the debug share we should enable the regular share data option. - if ( $request[ Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME ] ) { - update_option( Waf_Runner::SHARE_DATA_OPTION_NAME, 1 ); + // IP Block List + if ( isset( $request[ Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME ] ) ) { + update_option( Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME, $request[ Waf_Rules_Manager::IP_BLOCK_LIST_OPTION_NAME ] ); + } + if ( isset( $request[ Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME ] ) ) { + update_option( Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME, $request[ Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME ] ? '1' : '' ); } - update_option( Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME, $request[ Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME ] ? '1' : '' ); - } + // IP Allow List + if ( isset( $request[ Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ] ) ) { + update_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME, $request[ Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ] ); + } + if ( isset( $request[ Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME ] ) ) { + update_option( Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME, $request[ Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME ] ? '1' : '' ); + } - // Brute Force Protection - if ( isset( $request['brute_force_protection'] ) ) { - $enable_brute_force = (bool) $request['brute_force_protection']; - $brute_force_protection_toggled = + // Share Data + if ( isset( $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ) ) { + // If a user disabled the regular share we should disable the debug share data option. + if ( ! $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ) { + update_option( Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME, '' ); + } + + update_option( Waf_Runner::SHARE_DATA_OPTION_NAME, $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ? '1' : '' ); + } + + // Share Debug Data + if ( isset( $request[ Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME ] ) ) { + // If a user toggles the debug share we should enable the regular share data option. + if ( $request[ Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME ] ) { + update_option( Waf_Runner::SHARE_DATA_OPTION_NAME, 1 ); + } + + update_option( Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME, $request[ Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME ] ? '1' : '' ); + } + + return new WP_Error( + 'test_waf_test_1337', + 'test', + array( 'status' => 500 ) + ); + + // Brute Force Protection + if ( isset( $request['brute_force_protection'] ) ) { + $enable_brute_force = (bool) $request['brute_force_protection']; + $brute_force_protection_toggled = $enable_brute_force ? Brute_Force_Protection::enable() : Brute_Force_Protection::disable(); - if ( ! $brute_force_protection_toggled ) { - return new WP_Error( - $enable_brute_force + if ( ! $brute_force_protection_toggled ) { + return new WP_Error( + $enable_brute_force ? 'brute_force_protection_activation_failed' : 'brute_force_protection_deactivation_failed', - $enable_brute_force + $enable_brute_force ? __( 'Brute force protection could not be activated.', 'jetpack-waf' ) : __( 'Brute force protection could not be deactivated.', 'jetpack-waf' ), - array( 'status' => 500 ) - ); + array( 'status' => 500 ) + ); + } } - } - // Only attempt to update the WAF if the module is supported - if ( Waf_Runner::is_supported_environment() ) { - try { - Waf_Runner::update_waf(); - } catch ( Waf_Exception $e ) { - return $e->get_wp_error(); + // Only attempt to update the WAF if the module is supported + if ( Waf_Runner::is_supported_environment() ) { + try { + Waf_Runner::update_waf(); + } catch ( Waf_Exception $e ) { + return new WP_Error( + 'test_waf_failed_2', + $e->getMessage(), + array( 'status' => 500 ) + ); + } } - } - return self::waf(); + return self::waf(); + } catch ( Waf_Exception $e ) { + return new WP_Error( + 'test_waf_failed', + $e->getMessage(), + $e->get_wp_error() + ); + } } /** diff --git a/projects/plugins/protect/src/js/data/waf/use-waf-mutation.ts b/projects/plugins/protect/src/js/data/waf/use-waf-mutation.ts index a89d0abb9dd5f..809891b5b79bf 100644 --- a/projects/plugins/protect/src/js/data/waf/use-waf-mutation.ts +++ b/projects/plugins/protect/src/js/data/waf/use-waf-mutation.ts @@ -67,7 +67,9 @@ export default function useWafMutation(): UseMutationResult< // Reset the WAF config to its previous state. queryClient.setQueryData( [ QUERY_WAF_KEY ], context.initialValue ); - showErrorNotice( getCustomErrorMessage( error ) ); + console.error( error ); + console.error( error.data ); + throw error; }, } ); } diff --git a/projects/plugins/protect/tests/e2e/flows/connection.js b/projects/plugins/protect/tests/e2e/flows/connection.js new file mode 100644 index 0000000000000..e619cfd29daec --- /dev/null +++ b/projects/plugins/protect/tests/e2e/flows/connection.js @@ -0,0 +1,38 @@ +import { expect } from '_jetpack-e2e-commons/fixtures/base-test.js'; +import logger from '_jetpack-e2e-commons/logger.js'; +// import { AuthorizePage } from '_jetpack-e2e-commons/pages/wpcom/index.js'; + +/** + * Connect Jetpack Protect + * @param {page} page - Playwright page object + * @param {admin} admin - Playwright admin object + * @param {boolean} premium - Whether to connect with a Premium plan + */ +export async function connect( page, admin, premium = false ) { + logger.step( 'Connect Jetpack Protect' ); + + await admin.visitAdminPage( 'admin.php', 'page=jetpack-protect' ); + + logger.action( 'Checking for button "Get Jetpack Protect"' ); + const getJetpackProtectButton = page.getByRole( 'button', { name: 'Get Jetpack Protect' } ); + await expect( getJetpackProtectButton ).toBeVisible(); + await expect( getJetpackProtectButton ).toBeEnabled(); + + logger.action( 'Checking for button "Start for free"' ); + const startForFreeButton = page.getByRole( 'button', { name: 'Start for free' } ); + await expect( startForFreeButton ).toBeVisible(); + await expect( startForFreeButton ).toBeEnabled(); + + logger.action( 'Click the start for free button' ); + await startForFreeButton.click(); + + await expect( + page.getByText( 'vulnerabilities found' ).or( page.getByText( 'ready soon' ) ) + ).toBeVisible( { + timeout: 30_000, + } ); + + if ( premium ) { + // todo add purchase steps + } +} diff --git a/projects/plugins/protect/tests/e2e/package.json b/projects/plugins/protect/tests/e2e/package.json index e192b75e7f62c..3797190c125a3 100644 --- a/projects/plugins/protect/tests/e2e/package.json +++ b/projects/plugins/protect/tests/e2e/package.json @@ -14,7 +14,7 @@ "license": "GPL-2.0-or-later", "author": "Automattic", "scripts": { - "build": "pnpm jetpack build packages/assets packages/connection plugins/protect plugins/jetpack -v --no-pnpm-install --production", + "build": "pnpm jetpack build packages/assets packages/connection plugins/protect plugins/jetpack packages/waf packages/protect-models packages/protect-status -v --no-pnpm-install --production", "clean": "rm -rf output", "config:decrypt": "openssl enc -md sha1 -aes-256-cbc -d -pass env:CONFIG_KEY -in ./node_modules/_jetpack-e2e-commons/config/encrypted.enc -out ./config/local.cjs", "distclean": "rm -rf node_modules", diff --git a/projects/plugins/protect/tests/e2e/specs/start.test.js b/projects/plugins/protect/tests/e2e/specs/start.test.js index 75ff629fd4fc4..265d3ff3f06ca 100644 --- a/projects/plugins/protect/tests/e2e/specs/start.test.js +++ b/projects/plugins/protect/tests/e2e/specs/start.test.js @@ -1,8 +1,8 @@ import { prerequisitesBuilder } from '_jetpack-e2e-commons/env/prerequisites.js'; import { expect, test } from '_jetpack-e2e-commons/fixtures/base-test.js'; -import logger from '_jetpack-e2e-commons/logger.js'; +import { connect } from '../flows/connection'; -test.describe( 'Jetpack Protect plugin', () => { +test.describe( 'Jetpack Protect plugin - Pre-Connection', () => { test.beforeEach( async ( { page } ) => { await prerequisitesBuilder( page ) .withCleanEnv() @@ -11,27 +11,84 @@ test.describe( 'Jetpack Protect plugin', () => { .build(); } ); - test( 'Jetpack Protect admin page', async ( { page, admin } ) => { - logger.action( 'Visit the Jetpack Protect admin page and start for free' ); + test( 'Jetpack Protect firewall page', async ( { page, admin } ) => { + await connect( page, admin ); - await admin.visitAdminPage( 'admin.php', 'page=jetpack-protect' ); + // to do: should not need to manually reload the page here to ensure the waf module is available + await page.reload(); - logger.action( 'Checking for button "Get Jetpack Protect"' ); - const getJetpackProtectButton = page.getByRole( 'button', { name: 'Get Jetpack Protect' } ); - await expect( getJetpackProtectButton ).toBeVisible(); - await expect( getJetpackProtectButton ).toBeEnabled(); + await test.step( 'Navigate to firewall page', async () => { + await admin.visitAdminPage( 'admin.php', 'page=jetpack-protect#/firewall' ); + await expect( page.getByText( 'Firewall is on' ) ).toBeVisible(); + } ); - logger.action( 'Checking for button "Start for free"' ); - const startForFreeButton = page.getByRole( 'button', { name: 'Start for free' } ); - await expect( startForFreeButton ).toBeVisible(); - await expect( startForFreeButton ).toBeEnabled(); + // await test.step( 'Test the automatic firewall setting is disabled for free plans', async () => { + // const automaticFirewallToggle = page.locator( '#inspector-toggle-control-0' ); + // await expect( automaticFirewallToggle ).toBeDisabled(); + // await expect( automaticFirewallToggle ).not.toBeChecked(); + // } ); - logger.action( 'Click the start for free button' ); - await startForFreeButton.click(); + // await test.step( 'Test the brute force protection setting', async () => { + // const bruteForceToggle = page.locator( '#inspector-toggle-control-1' ); + // await expect( page.getByRole( 'heading', { name: 'Brute force protection' } ) ).toBeVisible(); + // await expect( bruteForceToggle ).toBeEnabled(); + // await expect( bruteForceToggle ).toBeChecked(); - logger.action( 'Checking for heading "Stay one step ahead of threats"' ); - await expect( - page.getByRole( 'heading', { name: 'Stay one step ahead of threats' } ) - ).toBeVisible(); + // // Test turning brute force off and then on again + // await bruteForceToggle.click(); + // await expect( page.getByText( 'Changes saved' ) ).toBeVisible( { + // timeout: 15_000, + // } ); + // await expect( bruteForceToggle ).toBeEnabled(); + // await expect( bruteForceToggle ).not.toBeChecked(); + // await bruteForceToggle.click(); + // await expect( page.getByText( 'Changes saved' ) ).toBeVisible(); + // await expect( bruteForceToggle ).toBeEnabled(); + // await expect( bruteForceToggle ).toBeChecked(); + // } ); + + await test.step( 'Test the IP block list settings', async () => { + const blockListTextarea = page.locator( '#jetpack_waf_ip_block_list' ); + const blockListToggle = page.locator( '#inspector-toggle-control-2' ); + + // Validate the default block list state + await expect( page.getByRole( 'heading', { name: 'Block IP addresses' } ) ).toBeVisible(); + await expect( blockListToggle ).toBeEnabled(); + await expect( blockListToggle ).not.toBeChecked(); + // Test turning the block list on + await blockListToggle.click(); + await expect( blockListToggle ).toBeEnabled(); + await expect( blockListToggle ).toBeChecked(); + await expect( blockListTextarea ).toBeVisible(); + + // Test adding an IP address to the block list + await blockListTextarea.fill( '192.168.1.1' ); + await page.getByRole( 'button', { name: 'Save block list' } ).click(); + await expect( blockListToggle ).toBeEnabled(); + await expect( page.getByText( 'Changes saved' ) ).toBeVisible(); + } ); + + await test.step( 'Test the IP allow list settings', async () => { + const trustedIPsToggle = page.locator( '#inspector-toggle-control-3' ); + const allowListTextarea = page.locator( '#jetpack_waf_ip_allow_list' ); + const saveAllowListButton = page.getByRole( 'button', { name: 'Save allow list' } ); + + // Validate the default allow list state + await expect( page.getByRole( 'heading', { name: 'Trusted IP addresses' } ) ).toBeVisible(); + await expect( trustedIPsToggle ).toBeEnabled(); + await expect( trustedIPsToggle ).not.toBeChecked(); + + // Test turning the allow list on + await trustedIPsToggle.click(); + await expect( trustedIPsToggle ).toBeEnabled(); + await expect( trustedIPsToggle ).toBeChecked(); + await expect( allowListTextarea ).toBeVisible(); + + // Test adding an IP address to the allow list + await allowListTextarea.fill( '192.168.1.1' ); + await saveAllowListButton.click(); + await expect( trustedIPsToggle ).toBeEnabled(); + await expect( page.getByText( 'Changes saved' ) ).toBeVisible(); + } ); } ); } );