| @@ -8,17 +8,20 @@ | ||
| 8 | 8 | |
| 9 | 9 | namespace WP_Defender\Component; |
| 10 | 10 | |
| 11 | 11 | use WP_CLI; |
| 12 | +use Countable; | |
| 12 | 13 | use Exception; |
| 13 | -use Countable; | |
| 14 | 14 | use Throwable; |
| 15 | 15 | use Faker\Factory; |
| 16 | +use WP_Filesystem_Base; | |
| 16 | 17 | use WP_CLI\ExitException; |
| 17 | 18 | use WP_Defender\Traits\IO; |
| 18 | 19 | use WP_Defender\Traits\Theme; |
| 19 | 20 | use WP_Defender\Traits\Plugin; |
| 20 | 21 | use WP_Defender\Traits\Formats; |
| 22 | +use WP_Defender\Behavior\WPMUDEV; | |
| 23 | +use WP_Defender\Component\Audit; | |
| 21 | 24 | use WP_Defender\Model\Audit_Log; |
| 22 | 25 | use WP_Defender\Model\Scan_Item; |
| 23 | 26 | use WP_Defender\Model\Lockout_Ip; |
| 24 | 27 | use WP_Defender\Model\Lockout_Log; |
| @@ -23,22 +26,22 @@ | ||
| 23 | 26 | use WP_Defender\Model\Lockout_Ip; |
| 24 | 27 | use WP_Defender\Model\Lockout_Log; |
| 25 | 28 | use WP_Defender\Controller\Dashboard; |
| 26 | 29 | use WP_Defender\Controller\Two_Factor; |
| 27 | -use WP_Defender\Model\Scan as Model_Scan; | |
| 30 | +use WP_Defender\Controller\Login_Access; | |
| 28 | 31 | use WP_Defender\Controller\Main_Setting; |
| 29 | 32 | use WP_Defender\Controller\Audit_Logging; |
| 30 | -use WP_Defender\Controller\Advanced_Tools; | |
| 33 | +use WP_Defender\Model\Scan as Model_Scan; | |
| 31 | 34 | use WP_Defender\Controller\Security_Tweaks; |
| 32 | 35 | use WP_Defender\Model\Setting\Login_Lockout; |
| 36 | +use WP_Defender\Behavior\Scan\Core_Integrity; | |
| 37 | +use WP_Defender\Controller\Blocklist_Monitor; | |
| 33 | 38 | use WP_Defender\Model\Setting\Password_Reset; |
| 34 | -use WP_Defender\Controller\Blocklist_Monitor; | |
| 35 | 39 | use WP_Defender\Model\Setting\Notfound_Lockout; |
| 36 | 40 | use WP_Defender\Model\Setting\Security_Headers; |
| 41 | +use WP_Defender\Component\Scan as Scan_Component; | |
| 42 | +use WP_Defender\Component\Logger\Rotation_Logger; | |
| 37 | 43 | use WP_Defender\Model\Setting\User_Agent_Lockout; |
| 38 | -use WP_Defender\Component\Logger\Rotation_Logger; | |
| 39 | -use WP_Defender\Component\Scan as Scan_Component; | |
| 40 | -use WP_Filesystem_Base; | |
| 41 | 44 | use function WP_CLI\Utils\format_items; |
| 42 | 45 | |
| 43 | 46 | if ( ! defined( 'WPINC' ) ) { |
| 44 | 47 | die; |
| @@ -60,26 +63,100 @@ | ||
| 60 | 63 | local_to_utc as protected; |
| 61 | 64 | moment_datetime_format_from as protected; |
| 62 | 65 | persistent_hub_datetime_format as protected; |
| 63 | 66 | time_since as protected; |
| 67 | + get_local_human_date as protected; | |
| 68 | + get_time_diff as protected; | |
| 64 | 69 | } |
| 65 | - use IO; | |
| 66 | - use Theme; | |
| 67 | - use Plugin; | |
| 70 | + use IO { | |
| 71 | + try_create_lock as protected; | |
| 72 | + release_cron_lock as protected; | |
| 73 | + remove_lock as protected; | |
| 74 | + acquire_cron_lock as protected; | |
| 75 | + compare_hashes as protected; | |
| 76 | + delete_dir as protected; | |
| 77 | + detect_line_ending as protected; | |
| 78 | + get_log_path as protected; | |
| 79 | + } | |
| 80 | + use Theme { | |
| 81 | + get_path_of_themes_dir as protected; | |
| 82 | + get_theme as protected; | |
| 83 | + get_theme_slugs as protected; | |
| 84 | + get_themes as protected; | |
| 85 | + is_active_theme as protected; | |
| 86 | + } | |
| 87 | + use Plugin { | |
| 88 | + check_plugin_on_wp_org as protected; | |
| 89 | + check_by_readme_file as protected; | |
| 90 | + get_abs_plugin_path_by_slug as protected; | |
| 91 | + get_active_plugin_names as protected; | |
| 92 | + get_plugin_details_by as protected; | |
| 93 | + get_plugin_directory_name as protected; | |
| 94 | + get_plugin_headers as protected; | |
| 95 | + get_plugin_relative_path as protected; | |
| 96 | + get_plugin_slugs as protected; | |
| 97 | + get_plugins as protected; | |
| 98 | + get_plugin_slug_by as protected; | |
| 99 | + handle_wp_org_response_by as protected; | |
| 100 | + is_active_plugin as protected; | |
| 101 | + is_likely_wporg_slug as protected; | |
| 102 | + ping_wp_org_by_plugin_slug as protected; | |
| 103 | + } | |
| 68 | 104 | |
| 69 | 105 | /** |
| 70 | - * This is a helper for scan module. | |
| 71 | - * #Options | |
| 106 | + * Run scans and manage scan results via WP-CLI. | |
| 107 | + * | |
| 108 | + * ## OPTIONS | |
| 109 | + * | |
| 72 | 110 | * <command> |
| 73 | - * : Value can be run - Perform a scan, e.g. 'run'-command or 'run ----type=detailed' for detailed result, | |
| 74 | - * or (un)ignore|delete|resolve to do the relevant task, | |
| 75 | - * or clear_logs to remove completed schedule logs. | |
| 111 | + * : Action to perform. | |
| 112 | + * --- | |
| 113 | + * options: | |
| 114 | + * - run | |
| 115 | + * - ignore | |
| 116 | + * - unignore | |
| 117 | + * - resolve | |
| 118 | + * - delete | |
| 119 | + * - clear_logs | |
| 120 | + * --- | |
| 121 | + * | |
| 76 | 122 | * [--type=<type>] |
| 77 | - * : Default, without values, is for all items, or core_integrity|plugin_integrity|vulnerability|suspicious_code|plugin_outdated|plugin_closed. | |
| 123 | + * : Filter by issue type. Omit to target all types. | |
| 124 | + * --- | |
| 125 | + * options: | |
| 126 | + * - detailed | |
| 127 | + * - core_integrity | |
| 128 | + * - plugin_integrity | |
| 129 | + * - vulnerability | |
| 130 | + * - suspicious_code | |
| 131 | + * - plugin_outdated | |
| 132 | + * - plugin_closed | |
| 133 | + * --- | |
| 78 | 134 | * |
| 79 | - * @param mixed $args Command arguments. | |
| 80 | - * @param mixed $options Command options. | |
| 135 | + * ## EXAMPLES | |
| 81 | 136 | * |
| 137 | + * # Run a full scan. | |
| 138 | + * $ wp defender scan run | |
| 139 | + * Success: All done! | |
| 140 | + * | |
| 141 | + * # Run a detailed scan with table output. | |
| 142 | + * $ wp defender scan run --type=detailed | |
| 143 | + * | |
| 144 | + * # Ignore all active core integrity issues. | |
| 145 | + * $ wp defender scan ignore --type=core_integrity | |
| 146 | + * | |
| 147 | + * # Resolve all active vulnerability issues. | |
| 148 | + * $ wp defender scan resolve --type=vulnerability | |
| 149 | + * | |
| 150 | + * # Delete all suspicious code files. | |
| 151 | + * $ wp defender scan delete --type=suspicious_code | |
| 152 | + * | |
| 153 | + * # Clear completed scan logs. | |
| 154 | + * $ wp defender scan clear_logs | |
| 155 | + * | |
| 156 | + * @param mixed $args Command arguments. | |
| 157 | + * @param mixed $options Command options. | |
| 158 | + * | |
| 82 | 159 | * @throws ExitException If an invalid command is provided. |
| 83 | 160 | */ |
| 84 | 161 | public function scan( $args, $options ) { |
| 85 | 162 | if ( ! is_array( $args ) || array() === $args ) { |
| @@ -86,9 +163,9 @@ | ||
| 86 | 163 | WP_CLI::error( 'Invalid command' ); |
| 87 | 164 | |
| 88 | 165 | return; |
| 89 | 166 | } |
| 90 | - [ $command ] = $args; | |
| 167 | + [$command] = $args; | |
| 91 | 168 | switch ( $command ) { |
| 92 | 169 | case 'run': |
| 93 | 170 | $this->scan_all( $options ); |
| 94 | 171 | break; |
| @@ -95,19 +172,11 @@ | ||
| 95 | 172 | case 'clear_logs': |
| 96 | 173 | $this->scan_clear_logs(); |
| 97 | 174 | break; |
| 98 | 175 | default: |
| 99 | - $commands = array( | |
| 100 | - 'ignore', | |
| 101 | - 'unignore', | |
| 102 | - 'resolve', | |
| 103 | - 'delete', | |
| 104 | - ); | |
| 176 | + $commands = array( 'ignore', 'unignore', 'resolve', 'delete' ); | |
| 105 | 177 | if ( in_array( $command, $commands, true ) ) { |
| 106 | - WP_CLI::confirm( | |
| 107 | - 'This can cause your site get fatal error and can\'t restore back unless you have a backup, are you sure to continue?', | |
| 108 | - $options | |
| 109 | - ); | |
| 178 | + WP_CLI::confirm( 'This can cause your site get fatal error and can\'t restore back unless you have a backup, are you sure to continue?', $options ); | |
| 110 | 179 | $this->scan_task( $command, $options ); |
| 111 | 180 | } else { |
| 112 | 181 | WP_CLI::error( sprintf( 'Unknown command %s', $command ) ); |
| 113 | 182 | } |
| @@ -115,69 +184,106 @@ | ||
| 115 | 184 | } |
| 116 | 185 | } |
| 117 | 186 | |
| 118 | 187 | /** |
| 119 | - * Split scan issue into file and dir. | |
| 188 | + * Starts a full scan based on the provided options. | |
| 120 | 189 | * |
| 121 | - * @param string|null $type Scan type. | |
| 122 | - * @param array $raw_data Array of raw scan data. | |
| 123 | - * | |
| 124 | - * @return array | |
| 190 | + * @param array $options Command options. | |
| 125 | 191 | */ |
| 126 | - private function split_scan_issue_into_file_and_dir( $type, $raw_data ): array { | |
| 127 | - // General case without type-param. | |
| 128 | - if ( null === $type ) { | |
| 129 | - if ( isset( $raw_data['file'] ) ) { | |
| 130 | - return array( | |
| 131 | - 'type' => 'file', | |
| 132 | - 'path' => $raw_data['file'], | |
| 133 | - ); | |
| 134 | - } elseif ( isset( $raw_data['base_slug'] ) ) { | |
| 135 | - return array( | |
| 136 | - 'type' => 'folder', | |
| 137 | - 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['base_slug'] ), | |
| 138 | - ); | |
| 139 | - } elseif ( isset( $raw_data['slug'] ) ) { | |
| 140 | - return array( | |
| 141 | - 'type' => 'folder', | |
| 142 | - 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['slug'] ), | |
| 143 | - ); | |
| 192 | + private function scan_all( $options ) { | |
| 193 | + $type = $options['type'] ?? null; | |
| 194 | + $is_detailed = false; | |
| 195 | + switch ( $type ) { | |
| 196 | + case null: | |
| 197 | + // All items. | |
| 198 | + $type = null; | |
| 199 | + break; | |
| 200 | + case 'detailed': | |
| 201 | + $is_detailed = true; | |
| 202 | + break; | |
| 203 | + default: | |
| 204 | + WP_CLI::error( sprintf( 'Unknown scan type %s', $type ) ); | |
| 205 | + break; | |
| 206 | + } | |
| 207 | + $scan_component = wd_di()->get( Scan_Component::class ); | |
| 208 | + if ( ! $scan_component->is_any_scan_type_active() ) { | |
| 209 | + WP_CLI::error( Scan_Component::get_emergency_scan_stop_text() ); | |
| 210 | + } | |
| 211 | + WP_CLI::log( 'Check if there is a scan ongoing...' ); | |
| 212 | + $scan = Model_Scan::get_active(); | |
| 213 | + if ( ! is_object( $scan ) ) { | |
| 214 | + WP_CLI::log( 'No active scan, creating...' ); | |
| 215 | + // Match the web-triggered flow: clear stale idle scans first so they don't skew "last scan" lookups. | |
| 216 | + wd_di()->get( Model_Scan::class )->delete_idle(); | |
| 217 | + delete_site_option( Core_Integrity::ISSUE_CHECKSUMS ); | |
| 218 | + $scan = Model_Scan::create(); | |
| 219 | + if ( is_wp_error( $scan ) ) { | |
| 220 | + WP_CLI::error( $scan->get_error_message() ); | |
| 144 | 221 | } |
| 222 | + $scan_component->gather_actioned_plugin_details(); | |
| 223 | + } else { | |
| 224 | + WP_CLI::log( 'Continue from last scan' ); | |
| 145 | 225 | } |
| 146 | - // Specific case with type-param. | |
| 147 | - if ( in_array( | |
| 148 | - $type, | |
| 149 | - array( | |
| 150 | - Scan_Item::TYPE_PLUGIN_OUTDATED, | |
| 151 | - Scan_Item::TYPE_PLUGIN_CLOSED, | |
| 152 | - ), | |
| 153 | - true | |
| 154 | - ) ) { | |
| 155 | - return array( | |
| 156 | - 'type' => 'folder', | |
| 157 | - 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['slug'] ), | |
| 158 | - ); | |
| 159 | - } elseif ( Scan_Item::TYPE_VULNERABILITY === $type ) { | |
| 160 | - return array( | |
| 161 | - 'type' => 'folder', | |
| 162 | - 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['base_slug'] ), | |
| 163 | - ); | |
| 164 | - } else { | |
| 165 | - return array( | |
| 166 | - 'type' => 'file', | |
| 167 | - 'path' => $raw_data['file'], | |
| 168 | - ); | |
| 226 | + // Start detailed scan. | |
| 227 | + if ( $is_detailed ) { | |
| 228 | + $start = microtime( true ); | |
| 169 | 229 | } |
| 230 | + $handler = wd_di()->get( Scan_Component::class ); | |
| 231 | + while ( $handler->process() === false ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedWhile | |
| 232 | + } | |
| 233 | + $scan = Model_Scan::get_last(); | |
| 234 | + if ( ! is_object( $scan ) || is_wp_error( $scan ) ) { | |
| 235 | + return; | |
| 236 | + } | |
| 237 | + $results = $scan->to_array(); | |
| 238 | + if ( is_array( $results ) && isset( $results['issues_items'] ) && array() !== $results['issues_items'] ) { | |
| 239 | + $count = is_array( $results['issues_items'] ) || $results['issues_items'] instanceof Countable ? count( $results['issues_items'] ) : 0; | |
| 240 | + // Finish detailed scan. | |
| 241 | + if ( $is_detailed ) { | |
| 242 | + format_items( 'table', $results['issues_items'], array( 'type', 'short_desc', 'full_path' ) ); | |
| 243 | + WP_CLI::log( sprintf( 'Saved %d items.', $count ) ); | |
| 244 | + $finish = microtime( true ) - $start; | |
| 245 | + WP_CLI::log( 'Scan takes ' . round( $finish, 2 ) . 's to process.' ); | |
| 246 | + } else { | |
| 247 | + WP_CLI::log( sprintf( 'Found %d issues.', $count ) ); | |
| 248 | + } | |
| 249 | + } | |
| 250 | + WP_CLI::success( 'All done!' ); | |
| 170 | 251 | } |
| 171 | 252 | |
| 172 | 253 | /** |
| 254 | + * Clear completed action scheduler logs. | |
| 255 | + */ | |
| 256 | + private function scan_clear_logs() { | |
| 257 | + $scan_component = wd_di()->get( Scan_Component::class ); | |
| 258 | + $result = $scan_component::clear_logs(); | |
| 259 | + $message = $result['success'] ?? $result['error'] ?? 'Malware scan logs are cleared'; | |
| 260 | + | |
| 261 | + WP_CLI::log( $message ); | |
| 262 | + } | |
| 263 | + | |
| 264 | + /** | |
| 173 | 265 | * Executes tasks based on the type of scan. |
| 174 | 266 | * |
| 175 | - * @param string $command The task to perform. | |
| 176 | - * @param mixed $options Command options. | |
| 267 | + * @param string $command The task to perform. | |
| 268 | + * @param mixed $options Command options. | |
| 177 | 269 | */ |
| 178 | 270 | private function scan_task( $command, $options ) { |
| 179 | - $type = $options['type'] ?? null; | |
| 271 | + $option_type = is_array( $options ) ? ( $options['type'] ?? null ) : null; | |
| 272 | + $type = is_string( $option_type ) && '' !== $option_type ? strtolower( $option_type ) : null; | |
| 273 | + if ( defender_is_wp_org_version() && in_array( | |
| 274 | + $type, | |
| 275 | + array( | |
| 276 | + Scan_Item::TYPE_VULNERABILITY, // TYPE_SUSPICIOUS const is not suitable for use. | |
| 277 | + 'suspicious_code', | |
| 278 | + | |
| 279 | + ), | |
| 280 | + true | |
| 281 | + ) ) { | |
| 282 | + WP_CLI::warning( 'A WPMU DEV subscription is required to use this command.' ); | |
| 283 | + return; | |
| 284 | + } | |
| 285 | + | |
| 180 | 286 | switch ( $type ) { |
| 181 | 287 | case null: |
| 182 | 288 | // All items. |
| 183 | 289 | $type = null; |
| @@ -205,9 +311,9 @@ | ||
| 205 | 311 | break; |
| 206 | 312 | } |
| 207 | 313 | $active = Model_Scan::get_active(); |
| 208 | 314 | if ( is_object( $active ) ) { |
| 209 | - return WP_CLI::error( 'A scan is running, you need to wait till it complete to continue' ); | |
| 315 | + WP_CLI::error( 'A scan is running, you need to wait till it complete to continue' ); | |
| 210 | 316 | } |
| 211 | 317 | $model = Model_Scan::get_last(); |
| 212 | 318 | if ( ! is_object( $model ) ) { |
| 213 | 319 | return; |
| @@ -236,38 +342,28 @@ | ||
| 236 | 342 | case 'resolve': |
| 237 | 343 | $items = $model->get_issues( $type, Scan_Item::STATUS_ACTIVE ); |
| 238 | 344 | $resolved = array(); |
| 239 | 345 | foreach ( $items as $item ) { |
| 240 | - if ( | |
| 241 | - in_array( | |
| 242 | - $item->type, | |
| 243 | - array( Scan_Item::TYPE_INTEGRITY, Scan_Item::TYPE_PLUGIN_CHECK ), | |
| 244 | - true | |
| 245 | - ) | |
| 246 | - ) { | |
| 346 | + if ( in_array( $item->type, array( Scan_Item::TYPE_INTEGRITY, Scan_Item::TYPE_PLUGIN_CHECK ), true ) ) { | |
| 247 | 347 | WP_CLI::log( sprintf( 'Reverting %s to original', $item->raw_data['file'] ) ); |
| 248 | 348 | $ret = $item->resolve(); |
| 249 | 349 | if ( ! is_wp_error( $ret ) ) { |
| 250 | 350 | $resolved[] = $item; |
| 251 | 351 | } else { |
| 252 | - return WP_CLI::error( $ret->get_error_message() ); | |
| 352 | + WP_CLI::error( $ret->get_error_message() ); | |
| 253 | 353 | } |
| 254 | 354 | } elseif ( Scan_Item::TYPE_SUSPICIOUS === $item->type ) { |
| 255 | 355 | // If this is content, we will try to delete them. |
| 256 | - $whitelist = array( | |
| 257 | - // wordfence waf. | |
| 258 | - ABSPATH . '/wordfence-waf.php', | |
| 259 | - // Any files inside plugins, if removed, can cause fatal error. | |
| 260 | - WP_CONTENT_DIR . '/plugins/', | |
| 261 | - // Any files inside themes. | |
| 356 | + $whitelist = array(// wordfence waf. | |
| 357 | + ABSPATH . '/wordfence-waf.php', // Any files inside plugins, if removed, can cause fatal error. | |
| 358 | + WP_CONTENT_DIR . '/plugins/', // Any files inside themes. | |
| 262 | 359 | $this->get_path_of_themes_dir(), |
| 263 | 360 | ); |
| 264 | 361 | $path = $item->raw_data['file']; |
| 265 | 362 | $can_delete = true; |
| 266 | - $current = ''; | |
| 267 | 363 | foreach ( $whitelist as $value ) { |
| 268 | 364 | $current = $value; |
| 269 | - if ( strpos( $value, $path ) > 0 ) { | |
| 365 | + if ( str_contains( $path, $value ) ) { | |
| 270 | 366 | // Ignore this. |
| 271 | 367 | $can_delete = false; |
| 272 | 368 | break; |
| 273 | 369 | } |
| @@ -273,17 +369,26 @@ | ||
| 273 | 369 | } |
| 274 | 370 | } |
| 275 | 371 | if ( false === $can_delete ) { |
| 276 | 372 | WP_CLI::log( sprintf( 'Ignore file %s as it is in %s', $path, $current ) ); |
| 277 | - } elseif ( wp_delete_file( $path ) ) { | |
| 373 | + } elseif ( ! is_dir( $path ) && wp_delete_file( $path ) ) { | |
| 278 | 374 | WP_CLI::log( sprintf( 'Delete file %s', $path ) ); |
| 279 | 375 | $model->remove_issue( $item->id ); |
| 280 | 376 | $resolved[] = $item; |
| 281 | 377 | } else { |
| 282 | - return WP_CLI::error( sprintf( "Can't delete file %s", $path ) ); | |
| 378 | + WP_CLI::error( sprintf( "Can't delete file %s", $path ) ); | |
| 283 | 379 | } |
| 380 | + } elseif ( Scan_Item::TYPE_VULNERABILITY === $item->type ) { | |
| 381 | + $ret = $item->resolve(); | |
| 382 | + if ( is_wp_error( $ret ) ) { | |
| 383 | + WP_CLI::error( $ret->get_error_message() ); | |
| 384 | + } elseif ( is_array( $ret ) && isset( $ret['type_notice'] ) && 'error' === $ret['type_notice'] ) { | |
| 385 | + WP_CLI::error( $ret['message'] ?? esc_html__( 'Unable to resolve vulnerability.', 'defender-security' ) ); | |
| 386 | + } else { | |
| 387 | + $model->remove_issue( $item->id ); | |
| 388 | + $resolved[] = $item; | |
| 389 | + } | |
| 284 | 390 | } |
| 285 | - // No result for Vulnerability, Outdated or Closed plugin types. | |
| 286 | 391 | } |
| 287 | 392 | WP_CLI::log( sprintf( 'Resolved %s items', count( $resolved ) ) ); |
| 288 | 393 | break; |
| 289 | 394 | case 'delete': |
| @@ -314,9 +419,9 @@ | ||
| 314 | 419 | $model->remove_issue( $item->id ); |
| 315 | 420 | $deleted[] = $item; |
| 316 | 421 | } else { |
| 317 | 422 | |
| 318 | - return WP_CLI::error( sprintf( "Can't delete %s: %s", $issue_type, $path ) ); | |
| 423 | + WP_CLI::error( sprintf( "Can't delete %s: %s", $issue_type, $path ) ); | |
| 319 | 424 | } |
| 320 | 425 | } elseif ( 'file' === $issue_type ) { |
| 321 | 426 | // Work with core_integrity, plugin_integrity or suspicious_code types. |
| 322 | 427 | if ( wp_delete_file( $path ) ) { |
| @@ -323,9 +428,9 @@ | ||
| 323 | 428 | WP_CLI::log( sprintf( 'Delete %s: %s', $issue_type, $path ) ); |
| 324 | 429 | $model->remove_issue( $item->id ); |
| 325 | 430 | $deleted[] = $item; |
| 326 | 431 | } else { |
| 327 | - return WP_CLI::error( sprintf( "Can't delete %s: %s", $issue_type, $path ) ); | |
| 432 | + WP_CLI::warning( sprintf( "Can't delete %s: %s", $issue_type, $path ) ); | |
| 328 | 433 | } |
| 329 | 434 | } |
| 330 | 435 | } |
| 331 | 436 | WP_CLI::log( sprintf( 'Deleted %s items', count( $deleted ) ) ); |
| @@ -335,12 +440,59 @@ | ||
| 335 | 440 | } |
| 336 | 441 | } |
| 337 | 442 | |
| 338 | 443 | /** |
| 444 | + * Split scan issue into file and dir. | |
| 445 | + * | |
| 446 | + * @param string|null $type Scan type. | |
| 447 | + * @param array $raw_data Array of raw scan data. | |
| 448 | + * | |
| 449 | + * @return array | |
| 450 | + */ | |
| 451 | + private function split_scan_issue_into_file_and_dir( $type, $raw_data ): array { | |
| 452 | + // General case without type-param. | |
| 453 | + if ( null === $type ) { | |
| 454 | + if ( isset( $raw_data['file'] ) ) { | |
| 455 | + return array( | |
| 456 | + 'type' => 'file', | |
| 457 | + 'path' => $raw_data['file'], | |
| 458 | + ); | |
| 459 | + } elseif ( isset( $raw_data['base_slug'] ) ) { | |
| 460 | + return array( | |
| 461 | + 'type' => 'folder', | |
| 462 | + 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['base_slug'] ), | |
| 463 | + ); | |
| 464 | + } elseif ( isset( $raw_data['slug'] ) ) { | |
| 465 | + return array( | |
| 466 | + 'type' => 'folder', | |
| 467 | + 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['slug'] ), | |
| 468 | + ); | |
| 469 | + } | |
| 470 | + } | |
| 471 | + | |
| 472 | + if ( in_array( $type, array( Scan_Item::TYPE_PLUGIN_OUTDATED, Scan_Item::TYPE_PLUGIN_CLOSED ), true ) ) { | |
| 473 | + return array( | |
| 474 | + 'type' => 'folder', | |
| 475 | + 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['slug'] ), | |
| 476 | + ); | |
| 477 | + } elseif ( Scan_Item::TYPE_VULNERABILITY === $type ) { | |
| 478 | + return array( | |
| 479 | + 'type' => 'folder', | |
| 480 | + 'path' => $this->get_abs_plugin_path_by_slug( $raw_data['base_slug'] ), | |
| 481 | + ); | |
| 482 | + } else { | |
| 483 | + return array( | |
| 484 | + 'type' => 'file', | |
| 485 | + 'path' => $raw_data['file'], | |
| 486 | + ); | |
| 487 | + } | |
| 488 | + } | |
| 489 | + | |
| 490 | + /** | |
| 339 | 491 | * Generate dummy data, use in unit tests. |
| 340 | 492 | * DO NOT USE IN PRODUCTION. |
| 341 | 493 | * |
| 342 | - * @param mixed $args Command arguments. | |
| 494 | + * @param mixed $args Command arguments. | |
| 343 | 495 | */ |
| 344 | 496 | public function seed( $args ) { |
| 345 | 497 | global $wp_filesystem; |
| 346 | 498 | // Initialize the WP filesystem, no more using 'file-put-contents' function. |
| @@ -352,59 +504,41 @@ | ||
| 352 | 504 | WP_CLI::error( 'Invalid command' ); |
| 353 | 505 | |
| 354 | 506 | return; |
| 355 | 507 | } |
| 508 | + if ( ! $this->is_testing_mode() ) { | |
| 509 | + return; | |
| 510 | + } | |
| 511 | + | |
| 356 | 512 | [ $command ] = $args; |
| 357 | 513 | switch ( $command ) { |
| 358 | 514 | case 'scan:core': |
| 515 | + WP_CLI::confirm( 'This will modify a WordPress core file (wp-load.php). Are you sure?', array() ); | |
| 516 | + | |
| 359 | 517 | $file_path = ABSPATH . 'wp-load.php'; |
| 360 | - $content = '//this make different'; | |
| 361 | - if ( $wp_filesystem->exists( $file_path ) ) { | |
| 362 | - $content = $wp_filesystem->get_contents( $file_path ) . $content; | |
| 518 | + if ( ! $wp_filesystem->exists( $file_path ) ) { | |
| 519 | + WP_CLI::error( sprintf( 'File does not exist: %s', $file_path ) ); | |
| 520 | + | |
| 521 | + return; | |
| 363 | 522 | } |
| 364 | - $wp_filesystem->put_contents( $file_path, $content ); | |
| 365 | - break; | |
| 366 | - case 'audit:logs': | |
| 367 | - $types = array( | |
| 368 | - Audit_Log::EVENT_TYPE_USER, | |
| 369 | - Audit_Log::EVENT_TYPE_SYSTEM, | |
| 370 | - Audit_Log::EVENT_TYPE_COMMENT, | |
| 371 | - Audit_Log::EVENT_TYPE_MEDIA, | |
| 372 | - Audit_Log::EVENT_TYPE_SETTINGS, | |
| 373 | - Audit_Log::EVENT_TYPE_CONTENT, | |
| 374 | - Audit_Log::EVENT_TYPE_MENU, | |
| 375 | - ); | |
| 376 | - $faker = Factory::create(); | |
| 377 | - for ( $i = 0; $i < 500; $i++ ) { | |
| 378 | - $log = new Audit_Log(); | |
| 379 | - $log->timestamp = Crypt::random_int( strtotime( '-31 days' ), time() ); | |
| 380 | - $log->event_type = $types[ array_rand( $types ) ]; | |
| 381 | - $log->action_type = $faker->word(); | |
| 382 | - $log->site_url = $faker->url(); | |
| 383 | - $log->user_id = $faker->numberBetween( 1, 1000 ); | |
| 384 | - $log->context = $faker->word(); | |
| 385 | - $log->ip = $faker->ipv4(); | |
| 386 | - $log->msg = $faker->word(); | |
| 387 | - $log->blog_id = $faker->numberBetween( 1, 100 ); | |
| 388 | - $log->synced = $faker->numberBetween( 0, 1 ); | |
| 389 | - $log->ttl = $faker->numberBetween( 1, 3600 ); | |
| 390 | - $log->save(); | |
| 523 | + $content = $wp_filesystem->get_contents( $file_path ); | |
| 524 | + if ( false === $content ) { | |
| 525 | + WP_CLI::error( sprintf( 'Could not read file: %s', $file_path ) ); | |
| 526 | + | |
| 527 | + return; | |
| 391 | 528 | } |
| 529 | + if ( str_contains( $content, '//this make different' ) ) { | |
| 530 | + WP_CLI::warning( 'File already seeded, skipping.' ); | |
| 531 | + | |
| 532 | + return; | |
| 533 | + } | |
| 534 | + $wp_filesystem->put_contents( $file_path, $content . '//this make different' ); | |
| 392 | 535 | break; |
| 393 | 536 | case 'ip:logs': |
| 537 | + WP_CLI::confirm( 'This will insert fake firewall lockout log entries into the database. Are you sure?', array() ); | |
| 394 | 538 | // We will generate randomly 10k logs in 3 months. |
| 395 | - $types = array( | |
| 396 | - Lockout_Log::AUTH_FAIL, | |
| 397 | - Lockout_Log::AUTH_LOCK, | |
| 398 | - Lockout_Log::ERROR_404, | |
| 399 | - Lockout_Log::LOCKOUT_404, | |
| 400 | - Lockout_Log::LOCKOUT_UA, | |
| 401 | - ); | |
| 402 | - $is_lock = array( | |
| 403 | - Lockout_Log::AUTH_LOCK, | |
| 404 | - Lockout_Log::LOCKOUT_404, | |
| 405 | - Lockout_Log::LOCKOUT_UA, | |
| 406 | - ); | |
| 539 | + $types = array( Lockout_Log::AUTH_FAIL, Lockout_Log::AUTH_LOCK, Lockout_Log::ERROR_404, Lockout_Log::LOCKOUT_404, Lockout_Log::LOCKOUT_UA ); | |
| 540 | + $is_lock = array( Lockout_Log::AUTH_LOCK, Lockout_Log::LOCKOUT_404, Lockout_Log::LOCKOUT_UA ); | |
| 407 | 541 | $faker = Factory::create(); |
| 408 | 542 | WP_CLI::log( $faker->ipv4 ); |
| 409 | 543 | $range = array( |
| 410 | 544 | 'today midnight' => array( 'now', 100 ), |
| @@ -419,9 +553,9 @@ | ||
| 419 | 553 | 'ua_lockout' => 0, |
| 420 | 554 | ); |
| 421 | 555 | $last_lockout = 0; |
| 422 | 556 | foreach ( $range as $date => $to ) { |
| 423 | - [ $to, $count ] = $to; | |
| 557 | + [$to, $count] = $to; | |
| 424 | 558 | for ( $i = 0; $i < $count; $i++ ) { |
| 425 | 559 | $model = new Lockout_Log(); |
| 426 | 560 | $model->ip = $faker->ipv4; |
| 427 | 561 | $model->type = $types[ array_rand( $types ) ]; |
| @@ -461,10 +595,11 @@ | ||
| 461 | 595 | } |
| 462 | 596 | |
| 463 | 597 | /** |
| 464 | 598 | * Clean up dummy data. |
| 599 | + * DO NOT USE IN PRODUCTION. | |
| 465 | 600 | * |
| 466 | - * @param mixed $args Command arguments. | |
| 601 | + * @param mixed $args Command arguments. | |
| 467 | 602 | */ |
| 468 | 603 | public function unseed( $args ) { |
| 469 | 604 | global $wp_filesystem; |
| 470 | 605 | // Initialize the WP filesystem, no more using 'file-put-contents' function. |
| @@ -476,15 +611,38 @@ | ||
| 476 | 611 | WP_CLI::error( 'Invalid command' ); |
| 477 | 612 | |
| 478 | 613 | return; |
| 479 | 614 | } |
| 615 | + if ( ! $this->is_testing_mode() ) { | |
| 616 | + return; | |
| 617 | + } | |
| 618 | + | |
| 480 | 619 | [ $command ] = $args; |
| 481 | 620 | switch ( $command ) { |
| 482 | 621 | case 'scan:core': |
| 483 | - $content = file_get_contents( ABSPATH . 'wp-load.php' ); | |
| 484 | - $wp_filesystem->put_contents( ABSPATH . 'wp-load.php', str_replace( '//this make different', '', $content ) ); | |
| 622 | + WP_CLI::confirm( 'This will revert the modification to wp-load.php. Are you sure?', array() ); | |
| 623 | + | |
| 624 | + $file_path = ABSPATH . 'wp-load.php'; | |
| 625 | + if ( ! $wp_filesystem->exists( $file_path ) ) { | |
| 626 | + WP_CLI::error( sprintf( 'File does not exist: %s', $file_path ) ); | |
| 627 | + | |
| 628 | + return; | |
| 629 | + } | |
| 630 | + $content = $wp_filesystem->get_contents( $file_path ); | |
| 631 | + if ( false === $content ) { | |
| 632 | + WP_CLI::error( sprintf( 'Could not read file: %s', $file_path ) ); | |
| 633 | + | |
| 634 | + return; | |
| 635 | + } | |
| 636 | + if ( ! str_contains( $content, '//this make different' ) ) { | |
| 637 | + WP_CLI::warning( 'Marker not found in file, nothing to revert.' ); | |
| 638 | + | |
| 639 | + return; | |
| 640 | + } | |
| 641 | + $wp_filesystem->put_contents( $file_path, str_replace( '//this make different', '', $content ) ); | |
| 485 | 642 | break; |
| 486 | 643 | case 'scan:suspicious': |
| 644 | + WP_CLI::confirm( 'This will delete the false-positive test file. Are you sure?', array() ); | |
| 487 | 645 | wp_delete_file( WP_CONTENT_DIR . '/false-positive.php' ); |
| 488 | 646 | break; |
| 489 | 647 | default: |
| 490 | 648 | break; |
| @@ -491,112 +649,96 @@ | ||
| 491 | 649 | } |
| 492 | 650 | } |
| 493 | 651 | |
| 494 | 652 | /** |
| 495 | - * Clears the audit log from Database. | |
| 496 | - * <command> reset | |
| 497 | - * This command must have this command | |
| 498 | - * Syntax: wp defender audit <command> | |
| 499 | - * Example: wp defender audit reset | |
| 653 | + * Manage audit logs via WP-CLI. | |
| 500 | 654 | * |
| 501 | - * @param mixed $args Command arguments. | |
| 655 | + * ## OPTIONS | |
| 656 | + * | |
| 657 | + * <command> | |
| 658 | + * : Action to perform. | |
| 659 | + * --- | |
| 660 | + * options: | |
| 661 | + * - reset | |
| 662 | + * - sync | |
| 663 | + * --- | |
| 664 | + * | |
| 665 | + * ## EXAMPLES | |
| 666 | + * | |
| 667 | + * # Delete all audit log entries from the database. | |
| 668 | + * $ wp defender audit reset | |
| 669 | + * All clear | |
| 670 | + * | |
| 671 | + * # Synchronize local audit logs with cloud history (Pro only). | |
| 672 | + * $ wp defender audit sync | |
| 673 | + * Sync completed. | |
| 674 | + * | |
| 675 | + * @param mixed $args Command arguments. | |
| 502 | 676 | */ |
| 503 | 677 | public function audit( $args ) { |
| 504 | 678 | if ( ! is_array( $args ) || array() === $args ) { |
| 505 | - WP_CLI::log( 'Invalid command, add necessary arguments. See below...' ); | |
| 506 | - WP_CLI::runcommand( 'defender audit --help' ); | |
| 679 | + WP_CLI::error( 'Invalid command, add necessary arguments. See below...', false ); | |
| 680 | + WP_CLI::runcommand( | |
| 681 | + 'defender audit --help', | |
| 682 | + array( | |
| 683 | + 'launch' => false, | |
| 684 | + 'exit_error' => false, | |
| 685 | + ) | |
| 686 | + ); | |
| 507 | 687 | |
| 508 | 688 | return; |
| 509 | 689 | } |
| 510 | - [ $command ] = $args; | |
| 690 | + | |
| 691 | + [$command] = $args; | |
| 511 | 692 | switch ( $command ) { |
| 512 | 693 | case 'reset': |
| 513 | - Audit_Log::truncate(); | |
| 514 | - delete_site_option( 'wd_audit_fetch_checkpoint' ); | |
| 694 | + wd_di()->get( Audit::class )->reset(); | |
| 515 | 695 | |
| 516 | 696 | WP_CLI::log( 'All clear' ); |
| 517 | 697 | break; |
| 518 | 698 | default: |
| 519 | - WP_CLI::log( 'Invalid command, add necessary arguments. See below...' ); | |
| 520 | - WP_CLI::runcommand( 'defender audit --help' ); | |
| 699 | + WP_CLI::error( 'Invalid command, add necessary arguments. See below...', false ); | |
| 700 | + WP_CLI::runcommand( | |
| 701 | + 'defender audit --help', | |
| 702 | + array( | |
| 703 | + 'launch' => false, | |
| 704 | + 'exit_error' => false, | |
| 705 | + ) | |
| 706 | + ); | |
| 521 | 707 | break; |
| 522 | 708 | } |
| 523 | 709 | } |
| 524 | 710 | |
| 525 | 711 | /** |
| 526 | - * Starts a full scan based on the provided options. | |
| 712 | + * Manage security headers via WP-CLI. | |
| 527 | 713 | * |
| 528 | - * @param array $options Command options. | |
| 529 | - */ | |
| 530 | - private function scan_all( $options ) { | |
| 531 | - $type = $options['type'] ?? null; | |
| 532 | - $is_detailed = false; | |
| 533 | - switch ( $type ) { | |
| 534 | - case null: | |
| 535 | - // All items. | |
| 536 | - $type = null; | |
| 537 | - break; | |
| 538 | - case 'detailed': | |
| 539 | - $is_detailed = true; | |
| 540 | - break; | |
| 541 | - default: | |
| 542 | - WP_CLI::error( sprintf( 'Unknown scan type %s', $type ) ); | |
| 543 | - break; | |
| 544 | - } | |
| 545 | - WP_CLI::log( 'Check if there is a scan ongoing...' ); | |
| 546 | - $scan = Model_Scan::get_active(); | |
| 547 | - if ( ! is_object( $scan ) ) { | |
| 548 | - WP_CLI::log( 'No active scan, creating...' ); | |
| 549 | - $scan = Model_Scan::create(); | |
| 550 | - if ( is_wp_error( $scan ) ) { | |
| 551 | - return WP_CLI::error( $scan->get_error_message() ); | |
| 552 | - } | |
| 553 | - wd_di()->get( Scan_Component::class )->gather_actioned_plugin_details(); | |
| 554 | - } else { | |
| 555 | - WP_CLI::log( 'Continue from last scan' ); | |
| 556 | - } | |
| 557 | - // Start detailed scan. | |
| 558 | - if ( $is_detailed ) { | |
| 559 | - $start = microtime( true ); | |
| 560 | - } | |
| 561 | - $handler = wd_di()->get( Scan_Component::class ); | |
| 562 | - $ret = false; | |
| 563 | - while ( $handler->process() === false ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedWhile | |
| 564 | - } | |
| 565 | - $scan = Model_Scan::get_last(); | |
| 566 | - if ( ! is_object( $scan ) || is_wp_error( $scan ) ) { | |
| 567 | - return; | |
| 568 | - } | |
| 569 | - $results = $scan->to_array(); | |
| 570 | - if ( is_array( $results ) && isset( $results['issues_items'] ) && array() !== $results['issues_items'] ) { | |
| 571 | - $count = is_array( $results['issues_items'] ) || $results['issues_items'] instanceof Countable | |
| 572 | - ? count( $results['issues_items'] ) | |
| 573 | - : 0; | |
| 574 | - // Finish detailed scan. | |
| 575 | - if ( $is_detailed ) { | |
| 576 | - format_items( 'table', $results['issues_items'], array( 'type', 'short_desc', 'full_path' ) ); | |
| 577 | - WP_CLI::log( sprintf( 'Saved %d items.', $count ) ); | |
| 578 | - $finish = microtime( true ) - $start; | |
| 579 | - WP_CLI::log( 'Scan takes ' . round( $finish, 2 ) . 's to process.' ); | |
| 580 | - } else { | |
| 581 | - WP_CLI::log( sprintf( 'Found %d issues.', $count ) ); | |
| 582 | - } | |
| 583 | - } | |
| 584 | - WP_CLI::success( 'All done!' ); | |
| 585 | - } | |
| 586 | - | |
| 587 | - /** | |
| 588 | - * This is a helper for Security header actions. | |
| 589 | - * #Options | |
| 714 | + * ## OPTIONS | |
| 715 | + * | |
| 590 | 716 | * <command> |
| 591 | - * : Value can be run - Check headers, or activate|deactivate all headers | |
| 592 | - * [--type=<type>] | |
| 593 | - * : Default is all | |
| 717 | + * : Action to perform. | |
| 718 | + * --- | |
| 719 | + * options: | |
| 720 | + * - check | |
| 721 | + * - activate | |
| 722 | + * - deactivate | |
| 723 | + * --- | |
| 724 | + * | |
| 594 | 725 | * ## EXAMPLES |
| 595 | - * wp defender security_headers check | |
| 596 | 726 | * |
| 597 | - * @param mixed $args Command arguments. | |
| 727 | + * # Check the current status of all security headers. | |
| 728 | + * $ wp defender security_headers check | |
| 729 | + * Success: Checking is ready. | |
| 598 | 730 | * |
| 731 | + * # Activate all security headers. | |
| 732 | + * $ wp defender security_headers activate | |
| 733 | + * Activating is ready. | |
| 734 | + * | |
| 735 | + * # Deactivate all security headers. | |
| 736 | + * $ wp defender security_headers deactivate | |
| 737 | + * Deactivating is ready. | |
| 738 | + * | |
| 739 | + * @param mixed $args Command arguments. | |
| 740 | + * | |
| 599 | 741 | * @throws ExitException|Exception If an invalid command is provided. |
| 600 | 742 | */ |
| 601 | 743 | public function security_headers( $args ) { |
| 602 | 744 | if ( ! is_array( $args ) || array() === $args ) { |
| @@ -603,15 +745,10 @@ | ||
| 603 | 745 | WP_CLI::error( 'Invalid command.' ); |
| 604 | 746 | |
| 605 | 747 | return; |
| 606 | 748 | } |
| 607 | - $model = new Security_Headers(); | |
| 608 | - if ( ! is_object( $model ) ) { | |
| 609 | - WP_CLI::error( 'Invalid model.' ); | |
| 610 | - | |
| 611 | - return; | |
| 612 | - } | |
| 613 | - [ $command ] = $args; | |
| 749 | + $model = new Security_Headers(); | |
| 750 | + [$command] = $args; | |
| 614 | 751 | switch ( $command ) { |
| 615 | 752 | case 'check': |
| 616 | 753 | $i = 1; |
| 617 | 754 | foreach ( $model->get_headers() as $header ) { |
| @@ -621,17 +758,17 @@ | ||
| 621 | 758 | } |
| 622 | 759 | WP_CLI::success( 'Checking is ready.' ); |
| 623 | 760 | break; |
| 624 | 761 | case 'activate': |
| 625 | - foreach ( $model->get_headers() as $header ) { | |
| 626 | - $model->{$header::$rule_slug} = true; | |
| 762 | + foreach ( $model->get_headers() as $rule_slug => $header ) { | |
| 763 | + $this->set_security_header_state( $model, $rule_slug, true ); | |
| 627 | 764 | } |
| 628 | 765 | $model->save(); |
| 629 | 766 | WP_CLI::log( 'Activating is ready.' ); |
| 630 | 767 | break; |
| 631 | 768 | case 'deactivate': |
| 632 | - foreach ( $model->get_headers() as $header ) { | |
| 633 | - $model->{$header::$rule_slug} = false; | |
| 769 | + foreach ( $model->get_headers() as $rule_slug => $header ) { | |
| 770 | + $this->set_security_header_state( $model, $rule_slug, false ); | |
| 634 | 771 | } |
| 635 | 772 | $model->save(); |
| 636 | 773 | WP_CLI::log( 'Deactivating is ready.' ); |
| 637 | 774 | break; |
| @@ -641,35 +778,78 @@ | ||
| 641 | 778 | } |
| 642 | 779 | } |
| 643 | 780 | |
| 644 | 781 | /** |
| 645 | - * This is a helper command to reset plugin settings. | |
| 646 | - * #Options | |
| 782 | + * Set a security header setting without using dynamic model properties. | |
| 783 | + * | |
| 784 | + * @param Security_Headers $model The security headers settings model. | |
| 785 | + * @param string $rule_slug The header rule slug. | |
| 786 | + * @param bool $enabled Whether the rule is enabled. | |
| 787 | + */ | |
| 788 | + private function set_security_header_state( Security_Headers $model, string $rule_slug, bool $enabled ): void { | |
| 789 | + switch ( $rule_slug ) { | |
| 790 | + case 'sh_xframe': | |
| 791 | + $model->sh_xframe = $enabled; | |
| 792 | + break; | |
| 793 | + case 'sh_xss_protection': | |
| 794 | + $model->sh_xss_protection = $enabled; | |
| 795 | + break; | |
| 796 | + case 'sh_content_type_options': | |
| 797 | + $model->sh_content_type_options = $enabled; | |
| 798 | + break; | |
| 799 | + case 'sh_strict_transport': | |
| 800 | + $model->sh_strict_transport = $enabled; | |
| 801 | + break; | |
| 802 | + case 'sh_referrer_policy': | |
| 803 | + $model->sh_referrer_policy = $enabled; | |
| 804 | + break; | |
| 805 | + case 'sh_feature_policy': | |
| 806 | + $model->sh_feature_policy = $enabled; | |
| 807 | + break; | |
| 808 | + } | |
| 809 | + } | |
| 810 | + | |
| 811 | + /** | |
| 812 | + * Manage plugin settings via WP-CLI. | |
| 813 | + * | |
| 814 | + * ## OPTIONS | |
| 815 | + * | |
| 647 | 816 | * <command> |
| 648 | - * Only allowed value is reset. | |
| 649 | - * Syntax: wp defender settings <command> | |
| 650 | - * Example: wp defender settings reset | |
| 817 | + * : Action to perform. | |
| 818 | + * --- | |
| 819 | + * options: | |
| 820 | + * - reset | |
| 821 | + * --- | |
| 651 | 822 | * |
| 652 | - * @param mixed $args Command arguments. | |
| 653 | - * @param mixed $options Command options. | |
| 823 | + * ## EXAMPLES | |
| 824 | + * | |
| 825 | + * # Reset all plugin settings to defaults. | |
| 826 | + * $ wp defender settings reset | |
| 827 | + * All cleared! | |
| 828 | + * | |
| 829 | + * @param mixed $args Command arguments. | |
| 830 | + * @param mixed $options Command options. | |
| 654 | 831 | */ |
| 655 | 832 | public function settings( $args, $options ) { |
| 656 | 833 | if ( ! is_array( $args ) || array() === $args ) { |
| 657 | - WP_CLI::log( 'Invalid command, add necessary arguments. See below...' ); | |
| 658 | - WP_CLI::runcommand( 'defender settings --help' ); | |
| 834 | + WP_CLI::error( 'Invalid command, add necessary arguments. See below...', false ); | |
| 835 | + WP_CLI::runcommand( | |
| 836 | + 'defender settings --help', | |
| 837 | + array( | |
| 838 | + 'launch' => false, | |
| 839 | + 'exit_error' => false, | |
| 840 | + ) | |
| 841 | + ); | |
| 659 | 842 | |
| 660 | 843 | return; |
| 661 | 844 | } |
| 662 | 845 | |
| 663 | - [ $command ] = $args; | |
| 846 | + [$command] = $args; | |
| 664 | 847 | switch ( $command ) { |
| 665 | 848 | case 'reset': |
| 666 | - WP_CLI::confirm( | |
| 667 | - 'This will completely reset the plugin settings, are you sure to continue?', | |
| 668 | - $options | |
| 669 | - ); | |
| 849 | + WP_CLI::confirm( 'This will completely reset the plugin settings, are you sure to continue?', $options ); | |
| 670 | 850 | // Analog Settings > Reset Settings. |
| 671 | - wd_di()->get( Advanced_Tools::class )->remove_settings(); | |
| 851 | + wd_di()->get( Login_Access::class )->remove_settings(); | |
| 672 | 852 | wd_di()->get( Audit_Logging::class )->remove_settings(); |
| 673 | 853 | wd_di()->get( Dashboard::class )->remove_settings(); |
| 674 | 854 | wd_di()->get( Security_Tweaks::class )->remove_settings(); |
| 675 | 855 | wd_di()->get( \WP_Defender\Controller\Scan::class )->remove_settings(); |
| @@ -678,50 +858,103 @@ | ||
| 678 | 858 | |
| 679 | 859 | wd_di()->get( \WP_Defender\Controller\Mask_Login::class )->remove_settings(); |
| 680 | 860 | wd_di()->get( \WP_Defender\Controller\Notification::class )->remove_settings(); |
| 681 | 861 | wd_di()->get( Two_Factor::class )->remove_settings(); |
| 682 | - wd_di()->get( Blocklist_Monitor::class )->remove_settings(); | |
| 683 | 862 | wd_di()->get( Main_Setting::class )->remove_settings(); |
| 684 | 863 | WP_CLI::log( 'All cleared!' ); |
| 685 | 864 | break; |
| 686 | 865 | default: |
| 687 | - WP_CLI::log( sprintf( 'Unknown command %s, use correct arguments. See below...', $command ) ); | |
| 688 | - WP_CLI::runcommand( 'defender settings --help' ); | |
| 866 | + WP_CLI::error( sprintf( 'Unknown command %s, use correct arguments. See below...', $command ), false ); | |
| 867 | + WP_CLI::runcommand( | |
| 868 | + 'defender settings --help', | |
| 869 | + array( | |
| 870 | + 'launch' => false, | |
| 871 | + 'exit_error' => false, | |
| 872 | + ) | |
| 873 | + ); | |
| 689 | 874 | break; |
| 690 | 875 | } |
| 691 | 876 | } |
| 692 | 877 | |
| 693 | 878 | /** |
| 694 | - * This toggle the firewall submodules, clears the data, show details or unlocks the IP from block list. | |
| 695 | - * Syntax: wp defender firewall <command> <args_1> <args_2> | |
| 696 | - * <command> clear|unblock|list|activate|deactivate | |
| 879 | + * Manage firewall submodules, data, and lockouts via WP-CLI. | |
| 697 | 880 | * |
| 698 | - * <args_1> Allowed values are: ip, user_agent, files and maxmind | |
| 699 | - * <args_2> Allowed values are: allowlist, blocklist, country_allowlist, country_blocklist, lockout and license_key | |
| 700 | - * Example: wp defender firewall clear ip allowlist | |
| 701 | - * Example: wp defender firewall unblock ip lockout --ips=127.0.0.1,236.211.38.221 | |
| 702 | - * Example: wp defender firewall list user_agent <status> | |
| 703 | - * Example: wp defender firewall activate submodule <submodule> | |
| 704 | - * Example: wp defender firewall deactivate submodule login_protection | |
| 705 | - * <status> Allowed values are: all, allowlist, blocklist. | |
| 706 | - * <submodule> Allowed values are: login_protection, 404_detection or user_agent. | |
| 881 | + * ## OPTIONS | |
| 707 | 882 | * |
| 708 | - * @param mixed $args Command arguments. | |
| 709 | - * @param mixed $options Command options. | |
| 883 | + * <command> | |
| 884 | + * : Action to perform. | |
| 885 | + * --- | |
| 886 | + * options: | |
| 887 | + * - clear | |
| 888 | + * - unblock | |
| 889 | + * - list | |
| 890 | + * - activate | |
| 891 | + * - deactivate | |
| 892 | + * --- | |
| 893 | + * | |
| 894 | + * <type> | |
| 895 | + * : The firewall data type to target (e.g. ip, user_agent, files, maxmind, submodule). | |
| 896 | + * | |
| 897 | + * [<field>] | |
| 898 | + * : The specific field or submodule to target. Defaults to 'all' for the list command. | |
| 899 | + * | |
| 900 | + * [--ips=<ips>] | |
| 901 | + * : Comma-separated list of IP addresses to unblock. Required for the unblock command. | |
| 902 | + * | |
| 903 | + * ## EXAMPLES | |
| 904 | + * | |
| 905 | + * # Clear the IP allowlist. | |
| 906 | + * $ wp defender firewall clear ip allowlist | |
| 907 | + * Firewall allowlist ip is cleared. | |
| 908 | + * | |
| 909 | + * # Unblock specific IPs from lockout. | |
| 910 | + * $ wp defender firewall unblock ip lockout --ips=127.0.0.1,236.211.38.221 | |
| 911 | + * Firewall lockout ip unblocked | |
| 912 | + * | |
| 913 | + * # List all user agent entries. | |
| 914 | + * $ wp defender firewall list user_agent all | |
| 915 | + * | |
| 916 | + * # Activate login protection submodule. | |
| 917 | + * $ wp defender firewall activate submodule login_protection | |
| 918 | + * Success: Firewall "Login Protection" has been activated. | |
| 919 | + * | |
| 920 | + * # Deactivate 404 detection submodule. | |
| 921 | + * $ wp defender firewall deactivate submodule 404_detection | |
| 922 | + * Success: Firewall "404 Detection" has been deactivated. | |
| 923 | + * | |
| 924 | + * @param mixed $args Command arguments. | |
| 925 | + * @param mixed $options Command options. | |
| 710 | 926 | */ |
| 711 | 927 | public function firewall( $args, $options ) { |
| 712 | - if ( ( is_array( $args ) || $args instanceof Countable ? count( $args ) : 0 ) <= 2 ) { | |
| 713 | - WP_CLI::log( 'Invalid command, add necessary arguments. See below...' ); | |
| 714 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 928 | + $arg_count = is_array( $args ) || $args instanceof Countable ? count( $args ) : 0; | |
| 929 | + if ( $arg_count < 2 ) { | |
| 930 | + WP_CLI::error( 'Invalid command, add necessary arguments. See below...', false ); | |
| 931 | + WP_CLI::runcommand( | |
| 932 | + 'defender firewall --help', | |
| 933 | + array( | |
| 934 | + 'launch' => false, | |
| 935 | + 'exit_error' => false, | |
| 936 | + ) | |
| 937 | + ); | |
| 715 | 938 | |
| 716 | 939 | return; |
| 717 | 940 | } |
| 718 | 941 | |
| 719 | - [ $command, $type, $field ] = $args; | |
| 720 | - if ( ! is_string( $type ) || '' === $type || ! is_string( $field ) || '' === $field ) { | |
| 721 | - WP_CLI::log( 'Invalid option.' ); | |
| 722 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 942 | + $command = $args[0]; | |
| 943 | + $type = $args[1]; | |
| 944 | + // Field is optional for the 'list' command — defaults to 'all'. | |
| 945 | + $field = $args[2] ?? ( 'list' === $command ? 'all' : '' ); | |
| 723 | 946 | |
| 947 | + if ( ! is_string( $type ) || '' === $type ) { | |
| 948 | + WP_CLI::error( 'Invalid option.', false ); | |
| 949 | + WP_CLI::runcommand( | |
| 950 | + 'defender firewall --help', | |
| 951 | + array( | |
| 952 | + 'launch' => false, | |
| 953 | + 'exit_error' => false, | |
| 954 | + ) | |
| 955 | + ); | |
| 956 | + | |
| 724 | 957 | return; |
| 725 | 958 | } |
| 726 | 959 | switch ( $command ) { |
| 727 | 960 | case 'clear': |
| @@ -745,41 +978,12 @@ | ||
| 745 | 978 | } |
| 746 | 979 | } |
| 747 | 980 | |
| 748 | 981 | /** |
| 749 | - * This clears the mask login settings. | |
| 750 | - * <command> clear | |
| 751 | - * This command must have this command | |
| 752 | - * Syntax: wp defender mask_login <command> | |
| 753 | - * Example: wp defender mask_login clear | |
| 754 | - * | |
| 755 | - * @param mixed $args Command arguments. | |
| 756 | - */ | |
| 757 | - public function mask_login( $args ) { | |
| 758 | - if ( ( is_array( $args ) || $args instanceof Countable ? count( $args ) : 0 ) < 1 ) { | |
| 759 | - WP_CLI::log( 'Invalid command, add necessary arguments. See below...' ); | |
| 760 | - WP_CLI::runcommand( 'defender mask_login --help' ); | |
| 761 | - | |
| 762 | - return; | |
| 763 | - } | |
| 764 | - | |
| 765 | - [ $command ] = $args; | |
| 766 | - switch ( $command ) { | |
| 767 | - case 'clear': | |
| 768 | - wd_di()->get( \WP_Defender\Model\Setting\Mask_Login::class )->delete(); | |
| 769 | - WP_CLI::log( 'Mask login settings cleared!' ); | |
| 770 | - break; | |
| 771 | - default: | |
| 772 | - WP_CLI::error( sprintf( 'Unknown command %s', $command ) ); | |
| 773 | - break; | |
| 774 | - } | |
| 775 | - } | |
| 776 | - | |
| 777 | - /** | |
| 778 | 982 | * Clears the firewall data based on the specified type and field. |
| 779 | 983 | * |
| 780 | - * @param string $type The type of data to clear. | |
| 781 | - * @param string $field The specific field to clear. | |
| 984 | + * @param string $type The type of data to clear. | |
| 985 | + * @param string $field The specific field to clear. | |
| 782 | 986 | */ |
| 783 | 987 | private function clear_firewall( $type, $field ) { |
| 784 | 988 | $type_default = array( 'ip', 'files', 'user_agent', 'maxmind' ); |
| 785 | 989 | $field_default = array( 'blocklist', 'allowlist', 'country_allowlist', 'country_blocklist', 'license_key' ); |
| @@ -784,17 +988,29 @@ | ||
| 784 | 988 | $type_default = array( 'ip', 'files', 'user_agent', 'maxmind' ); |
| 785 | 989 | $field_default = array( 'blocklist', 'allowlist', 'country_allowlist', 'country_blocklist', 'license_key' ); |
| 786 | 990 | |
| 787 | 991 | if ( ! in_array( $type, $type_default, true ) ) { |
| 788 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $type ) ); | |
| 789 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 992 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $type ), false ); | |
| 993 | + WP_CLI::runcommand( | |
| 994 | + 'defender firewall --help', | |
| 995 | + array( | |
| 996 | + 'launch' => false, | |
| 997 | + 'exit_error' => false, | |
| 998 | + ) | |
| 999 | + ); | |
| 790 | 1000 | |
| 791 | 1001 | return; |
| 792 | 1002 | } |
| 793 | 1003 | |
| 794 | 1004 | if ( ! in_array( $field, $field_default, true ) ) { |
| 795 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $field ) ); | |
| 796 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1005 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $field ), false ); | |
| 1006 | + WP_CLI::runcommand( | |
| 1007 | + 'defender firewall --help', | |
| 1008 | + array( | |
| 1009 | + 'launch' => false, | |
| 1010 | + 'exit_error' => false, | |
| 1011 | + ) | |
| 1012 | + ); | |
| 797 | 1013 | |
| 798 | 1014 | return; |
| 799 | 1015 | } |
| 800 | 1016 | |
| @@ -828,9 +1044,9 @@ | ||
| 828 | 1044 | $model->save(); |
| 829 | 1045 | } elseif ( 'maxmind' === $type ) { |
| 830 | 1046 | try { |
| 831 | 1047 | $model = wd_di()->get( \WP_Defender\Model\Setting\Blacklist_Lockout::class ); |
| 832 | - if ( ! is_null( $model->geodb_path ) && is_file( $model->geodb_path ) ) { | |
| 1048 | + if ( is_string( $model->geodb_path ) && is_file( $model->geodb_path ) ) { | |
| 833 | 1049 | wp_delete_file( $model->geodb_path ); |
| 834 | 1050 | } |
| 835 | 1051 | $model->maxmind_license_key = ''; |
| 836 | 1052 | $model->geodb_path = ''; |
| @@ -843,13 +1059,39 @@ | ||
| 843 | 1059 | WP_CLI::log( sprintf( 'Firewall %s %s is cleared.', str_replace( '_', ' ', $field ), $type ) ); |
| 844 | 1060 | } |
| 845 | 1061 | |
| 846 | 1062 | /** |
| 1063 | + * Rename a field to its original model field name. | |
| 1064 | + * | |
| 1065 | + * @param string $field The field name to rename. | |
| 1066 | + * | |
| 1067 | + * @return string The renamed field name. | |
| 1068 | + */ | |
| 1069 | + private function rename_field( $field ) { | |
| 1070 | + if ( '' !== $field ) { | |
| 1071 | + return str_replace( array( 'allow', 'block' ), array( 'white', 'black' ), $field ); | |
| 1072 | + } | |
| 1073 | + | |
| 1074 | + return ''; | |
| 1075 | + } | |
| 1076 | + | |
| 1077 | + /** | |
| 1078 | + * Check if the specified field is related to country settings. | |
| 1079 | + * | |
| 1080 | + * @param string $field The field to check. | |
| 1081 | + * | |
| 1082 | + * @return bool True if the field is related to country settings, false otherwise. | |
| 1083 | + */ | |
| 1084 | + private function is_country( $field ) { | |
| 1085 | + return ( 'country_whitelist' === $field || 'country_blacklist' === $field ); | |
| 1086 | + } | |
| 1087 | + | |
| 1088 | + /** | |
| 847 | 1089 | * Unblocks the specified IPs from the firewall. |
| 848 | 1090 | * |
| 849 | - * @param string $type The type of data to unblock. | |
| 850 | - * @param string $field The specific field to unblock. | |
| 851 | - * @param array $options Command options including IPs to unblock. | |
| 1091 | + * @param string $type The type of data to unblock. | |
| 1092 | + * @param string $field The specific field to unblock. | |
| 1093 | + * @param array $options Command options including IPs to unblock. | |
| 852 | 1094 | */ |
| 853 | 1095 | private function unblock_firewall( $type, $field, $options ) { |
| 854 | 1096 | $type_default = array( 'ip' ); |
| 855 | 1097 | $field_default = array( 'lockout' ); |
| @@ -854,17 +1096,29 @@ | ||
| 854 | 1096 | $type_default = array( 'ip' ); |
| 855 | 1097 | $field_default = array( 'lockout' ); |
| 856 | 1098 | |
| 857 | 1099 | if ( ! in_array( $type, $type_default, true ) ) { |
| 858 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $type ) ); | |
| 859 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1100 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $type ), false ); | |
| 1101 | + WP_CLI::runcommand( | |
| 1102 | + 'defender firewall --help', | |
| 1103 | + array( | |
| 1104 | + 'launch' => false, | |
| 1105 | + 'exit_error' => false, | |
| 1106 | + ) | |
| 1107 | + ); | |
| 860 | 1108 | |
| 861 | 1109 | return; |
| 862 | 1110 | } |
| 863 | 1111 | |
| 864 | 1112 | if ( ! in_array( $field, $field_default, true ) ) { |
| 865 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $field ) ); | |
| 866 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1113 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $field ), false ); | |
| 1114 | + WP_CLI::runcommand( | |
| 1115 | + 'defender firewall --help', | |
| 1116 | + array( | |
| 1117 | + 'launch' => false, | |
| 1118 | + 'exit_error' => false, | |
| 1119 | + ) | |
| 1120 | + ); | |
| 867 | 1121 | |
| 868 | 1122 | return; |
| 869 | 1123 | } |
| 870 | 1124 | |
| @@ -876,10 +1130,16 @@ | ||
| 876 | 1130 | $model->status = Lockout_Ip::STATUS_NORMAL; |
| 877 | 1131 | $model->save(); |
| 878 | 1132 | } |
| 879 | 1133 | } else { |
| 880 | - WP_CLI::log( 'Option \'ips\' is not provided. See below...' ); | |
| 881 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1134 | + WP_CLI::error( 'Option \'ips\' is not provided. See below...', false ); | |
| 1135 | + WP_CLI::runcommand( | |
| 1136 | + 'defender firewall --help', | |
| 1137 | + array( | |
| 1138 | + 'launch' => false, | |
| 1139 | + 'exit_error' => false, | |
| 1140 | + ) | |
| 1141 | + ); | |
| 882 | 1142 | |
| 883 | 1143 | return; |
| 884 | 1144 | } |
| 885 | 1145 | |
| @@ -889,10 +1149,10 @@ | ||
| 889 | 1149 | /** |
| 890 | 1150 | * Lists details for the firewall based on the specified type and field. |
| 891 | 1151 | * Example: wp defender firewall list user_agent all |
| 892 | 1152 | * |
| 893 | - * @param string $type The type of data to list. | |
| 894 | - * @param string $field The specific field to list. | |
| 1153 | + * @param string $type The type of data to list. | |
| 1154 | + * @param string $field The specific field to list. | |
| 895 | 1155 | * |
| 896 | 1156 | * @since v2.6.4. Add the details for User Agent Banning. |
| 897 | 1157 | */ |
| 898 | 1158 | private function list_firewall( $type, $field ) { |
| @@ -898,16 +1158,28 @@ | ||
| 898 | 1158 | private function list_firewall( $type, $field ) { |
| 899 | 1159 | $type_default = array( 'user_agent' ); |
| 900 | 1160 | $field_default = array( 'all', 'allowlist', 'blocklist' ); |
| 901 | 1161 | if ( ! in_array( $type, $type_default, true ) ) { |
| 902 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $type ) ); | |
| 903 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1162 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $type ), false ); | |
| 1163 | + WP_CLI::runcommand( | |
| 1164 | + 'defender firewall --help', | |
| 1165 | + array( | |
| 1166 | + 'launch' => false, | |
| 1167 | + 'exit_error' => false, | |
| 1168 | + ) | |
| 1169 | + ); | |
| 904 | 1170 | |
| 905 | 1171 | return; |
| 906 | 1172 | } |
| 907 | 1173 | if ( ! in_array( $field, $field_default, true ) ) { |
| 908 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $field ) ); | |
| 909 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1174 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $field ), false ); | |
| 1175 | + WP_CLI::runcommand( | |
| 1176 | + 'defender firewall --help', | |
| 1177 | + array( | |
| 1178 | + 'launch' => false, | |
| 1179 | + 'exit_error' => false, | |
| 1180 | + ) | |
| 1181 | + ); | |
| 910 | 1182 | |
| 911 | 1183 | return; |
| 912 | 1184 | } |
| 913 | 1185 | $model = wd_di()->get( User_Agent_Lockout::class ); |
| @@ -930,22 +1202,34 @@ | ||
| 930 | 1202 | * Change status of Firewall submodules: login_protection, 404_detection or user_agent. |
| 931 | 1203 | * Example: wp defender firewall activate submodule user_agent |
| 932 | 1204 | * Example: wp defender firewall deactivate submodule login_protection |
| 933 | 1205 | * |
| 934 | - * @param string $key_word The keyword to identify the action. | |
| 935 | - * @param string $submodule The submodule to toggle. | |
| 936 | - * @param string $action The action to perform (activate or deactivate). | |
| 1206 | + * @param string $key_word The keyword to identify the action. | |
| 1207 | + * @param string $submodule The submodule to toggle. | |
| 1208 | + * @param string $action The action to perform (activate or deactivate). | |
| 937 | 1209 | */ |
| 938 | 1210 | private function toggle_firewall_submodule( $key_word, $submodule, $action ) { |
| 939 | 1211 | if ( 'submodule' !== $key_word ) { |
| 940 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $key_word ) ); | |
| 941 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1212 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $key_word ), false ); | |
| 1213 | + WP_CLI::runcommand( | |
| 1214 | + 'defender firewall --help', | |
| 1215 | + array( | |
| 1216 | + 'launch' => false, | |
| 1217 | + 'exit_error' => false, | |
| 1218 | + ) | |
| 1219 | + ); | |
| 942 | 1220 | |
| 943 | 1221 | return; |
| 944 | 1222 | } |
| 945 | 1223 | if ( ! in_array( $submodule, array( 'login_protection', '404_detection', 'user_agent' ), true ) ) { |
| 946 | - WP_CLI::log( sprintf( 'Invalid option %s. See below...', $submodule ) ); | |
| 947 | - WP_CLI::runcommand( 'defender firewall --help' ); | |
| 1224 | + WP_CLI::error( sprintf( 'Invalid option %s. See below...', $submodule ), false ); | |
| 1225 | + WP_CLI::runcommand( | |
| 1226 | + 'defender firewall --help', | |
| 1227 | + array( | |
| 1228 | + 'launch' => false, | |
| 1229 | + 'exit_error' => false, | |
| 1230 | + ) | |
| 1231 | + ); | |
| 948 | 1232 | |
| 949 | 1233 | return; |
| 950 | 1234 | } |
| 951 | 1235 | // Get submodule slug. |
| @@ -979,50 +1263,98 @@ | ||
| 979 | 1263 | WP_CLI::success( sprintf( 'Firewall "%s" has been %s.', $submodule, $text ) ); |
| 980 | 1264 | } |
| 981 | 1265 | |
| 982 | 1266 | /** |
| 983 | - * Rename a field to its original model field name. | |
| 1267 | + * Check if the testing mode is enabled. | |
| 1268 | + * Outputs an error and returns false if WP_DEFENDER_TESTING is not defined and true. | |
| 984 | 1269 | * |
| 985 | - * @param string $field The field name to rename. | |
| 986 | - * | |
| 987 | - * @return string The renamed field name. | |
| 1270 | + * @return bool | |
| 988 | 1271 | */ |
| 989 | - private function rename_field( $field ) { | |
| 990 | - if ( '' !== $field ) { | |
| 991 | - return str_replace( array( 'allow', 'block' ), array( 'white', 'black' ), $field ); | |
| 1272 | + private function is_testing_mode(): bool { | |
| 1273 | + if ( ! defined( 'WP_DEFENDER_TESTING' ) || ! WP_DEFENDER_TESTING ) { | |
| 1274 | + WP_CLI::error( 'This command is intended for testing only. Define WP_DEFENDER_TESTING as true to proceed.' ); | |
| 1275 | + | |
| 1276 | + return false; | |
| 992 | 1277 | } |
| 993 | 1278 | |
| 994 | - return ''; | |
| 1279 | + return true; | |
| 995 | 1280 | } |
| 996 | 1281 | |
| 997 | 1282 | /** |
| 998 | - * Check if the specified field is related to country settings. | |
| 1283 | + * Force Bulk Password Reset. | |
| 1284 | + * <command> | |
| 1285 | + * : Action to perform. | |
| 1286 | + * --- | |
| 1287 | + * options: | |
| 1288 | + * - clear | |
| 1289 | + * --- | |
| 999 | 1290 | * |
| 1000 | - * @param string $field The field to check. | |
| 1291 | + * ## EXAMPLES | |
| 1001 | 1292 | * |
| 1002 | - * @return bool True if the field is related to country settings, false otherwise. | |
| 1293 | + * # Reset all mask login settings to defaults. | |
| 1294 | + * $ wp defender mask_login clear | |
| 1295 | + * Mask login settings cleared! | |
| 1296 | + * | |
| 1297 | + * @param mixed $args Command arguments. | |
| 1003 | 1298 | */ |
| 1004 | - private function is_country( $field ) { | |
| 1005 | - return ( 'country_whitelist' === $field || 'country_blacklist' === $field ); | |
| 1299 | + public function mask_login( $args ) { | |
| 1300 | + if ( ( is_array( $args ) || $args instanceof Countable ? count( $args ) : 0 ) < 1 ) { | |
| 1301 | + WP_CLI::error( 'Invalid command, add necessary arguments. See below...', false ); | |
| 1302 | + WP_CLI::runcommand( | |
| 1303 | + 'defender mask_login --help', | |
| 1304 | + array( | |
| 1305 | + 'launch' => false, | |
| 1306 | + 'exit_error' => false, | |
| 1307 | + ) | |
| 1308 | + ); | |
| 1309 | + | |
| 1310 | + return; | |
| 1311 | + } | |
| 1312 | + | |
| 1313 | + [$command] = $args; | |
| 1314 | + switch ( $command ) { | |
| 1315 | + case 'clear': | |
| 1316 | + wd_di()->get( \WP_Defender\Model\Setting\Mask_Login::class )->delete(); | |
| 1317 | + WP_CLI::log( 'Mask login settings cleared!' ); | |
| 1318 | + break; | |
| 1319 | + default: | |
| 1320 | + WP_CLI::error( sprintf( 'Unknown command %s', $command ) ); | |
| 1321 | + break; | |
| 1322 | + } | |
| 1006 | 1323 | } |
| 1007 | 1324 | |
| 1008 | 1325 | /** |
| 1009 | - * Force Bulk Password Reset. | |
| 1326 | + * Manage bulk password reset via WP-CLI. | |
| 1327 | + * | |
| 1328 | + * ## OPTIONS | |
| 1329 | + * | |
| 1010 | 1330 | * <command> |
| 1011 | - * : Value can be force|undo | |
| 1012 | - * Syntax: wp defender password_reset <command> | |
| 1013 | - * Example: wp defender password_reset force | |
| 1331 | + * : Action to perform. | |
| 1332 | + * --- | |
| 1333 | + * options: | |
| 1334 | + * - force | |
| 1335 | + * - undo | |
| 1336 | + * --- | |
| 1014 | 1337 | * |
| 1015 | - * @param mixed $args Command arguments. | |
| 1338 | + * ## EXAMPLES | |
| 1339 | + * | |
| 1340 | + * # Force all users to reset their password on next login. | |
| 1341 | + * $ wp defender password_reset force | |
| 1342 | + * | |
| 1343 | + * # Cancel a previously forced password reset. | |
| 1344 | + * $ wp defender password_reset undo | |
| 1345 | + * Passwords reset is no longer required. | |
| 1346 | + * | |
| 1347 | + * @param mixed $args Command arguments. | |
| 1016 | 1348 | */ |
| 1017 | 1349 | public function password_reset( $args ) { |
| 1018 | 1350 | if ( ( is_array( $args ) || $args instanceof Countable ? count( $args ) : 0 ) < 1 ) { |
| 1019 | - WP_CLI::log( 'Invalid command.' ); | |
| 1351 | + WP_CLI::error( 'Invalid command.' ); | |
| 1020 | 1352 | |
| 1021 | 1353 | return; |
| 1022 | 1354 | } |
| 1023 | 1355 | |
| 1024 | - [ $command ] = $args; | |
| 1356 | + [$command] = $args; | |
| 1025 | 1357 | switch ( $command ) { |
| 1026 | 1358 | case 'force': |
| 1027 | 1359 | // Get the model instance. |
| 1028 | 1360 | $model = wd_di()->get( Password_Reset::class ); |
| @@ -1028,12 +1360,9 @@ | ||
| 1028 | 1360 | $model = wd_di()->get( Password_Reset::class ); |
| 1029 | 1361 | $model->expire_force = true; |
| 1030 | 1362 | $model->force_time = time(); |
| 1031 | 1363 | $model->save(); |
| 1032 | - $message = sprintf( | |
| 1033 | - 'Passwords created before %s are required to be reset upon next login.', | |
| 1034 | - $this->format_date_time( $model->force_time ) | |
| 1035 | - ); | |
| 1364 | + $message = sprintf( 'Passwords created before %s are required to be reset upon next login.', $this->format_date_time( $model->force_time ) ); | |
| 1036 | 1365 | WP_CLI::log( $message ); |
| 1037 | 1366 | break; |
| 1038 | 1367 | case 'undo': |
| 1039 | 1368 | $model = wd_di()->get( Password_Reset::class ); |
| @@ -1047,36 +1376,42 @@ | ||
| 1047 | 1376 | } |
| 1048 | 1377 | } |
| 1049 | 1378 | |
| 1050 | 1379 | /** |
| 1051 | - * Clear completed action scheduler logs. | |
| 1052 | - */ | |
| 1053 | - private function scan_clear_logs() { | |
| 1054 | - $scan_component = wd_di()->get( Scan_Component::class ); | |
| 1055 | - $result = $scan_component::clear_logs(); | |
| 1056 | - $message = $result['success'] ?? $result['error'] ?? 'Malware scan logs are cleared'; | |
| 1057 | - | |
| 1058 | - WP_CLI::log( $message ); | |
| 1059 | - } | |
| 1060 | - | |
| 1061 | - /** | |
| 1062 | - * Delete old logs. | |
| 1063 | - * <command> delete | |
| 1064 | - * This command must have this command | |
| 1065 | - * Syntax: wp defender logs <command> | |
| 1066 | - * Example: wp defender logs delete | |
| 1380 | + * Manage Defender's internal log files. | |
| 1067 | 1381 | * |
| 1068 | - * @param mixed $args Command arguments. | |
| 1382 | + * ## OPTIONS | |
| 1383 | + * | |
| 1384 | + * <command> | |
| 1385 | + * : Action to perform. | |
| 1386 | + * --- | |
| 1387 | + * options: | |
| 1388 | + * - delete | |
| 1389 | + * --- | |
| 1390 | + * | |
| 1391 | + * ## EXAMPLES | |
| 1392 | + * | |
| 1393 | + * # Delete log files older than one week. | |
| 1394 | + * $ wp defender logs delete | |
| 1395 | + * Logs older than a week have been deleted. | |
| 1396 | + * | |
| 1397 | + * @param mixed $args Command arguments. | |
| 1069 | 1398 | */ |
| 1070 | 1399 | public function logs( $args ) { |
| 1071 | 1400 | if ( ( is_array( $args ) || $args instanceof Countable ? count( $args ) : 0 ) < 1 ) { |
| 1072 | - WP_CLI::log( 'Invalid command, add necessary arguments. See below...' ); | |
| 1073 | - WP_CLI::runcommand( 'defender logs --help' ); | |
| 1401 | + WP_CLI::error( 'Invalid command, add necessary arguments. See below...', false ); | |
| 1402 | + WP_CLI::runcommand( | |
| 1403 | + 'defender logs --help', | |
| 1404 | + array( | |
| 1405 | + 'launch' => false, | |
| 1406 | + 'exit_error' => false, | |
| 1407 | + ) | |
| 1408 | + ); | |
| 1074 | 1409 | |
| 1075 | 1410 | return; |
| 1076 | 1411 | } |
| 1077 | 1412 | |
| 1078 | - [ $command ] = $args; | |
| 1413 | + [$command] = $args; | |
| 1079 | 1414 | |
| 1080 | 1415 | switch ( $command ) { |
| 1081 | 1416 | case 'delete': |
| 1082 | 1417 | $rotation_logger = wd_di()->get( Rotation_Logger::class ); |
| @@ -1089,24 +1424,45 @@ | ||
| 1089 | 1424 | } |
| 1090 | 1425 | } |
| 1091 | 1426 | |
| 1092 | 1427 | /** |
| 1093 | - * This is a helper for Captcha actions. | |
| 1094 | - * Syntax: wp defender captcha <command> | |
| 1095 | - * <command> activate|deactivate|clear | |
| 1096 | - * Example: wp defender captcha activate | |
| 1428 | + * Manage CAPTCHA settings via WP-CLI. | |
| 1097 | 1429 | * |
| 1098 | - * @param mixed $args Command arguments. | |
| 1430 | + * ## OPTIONS | |
| 1431 | + * | |
| 1432 | + * <command> | |
| 1433 | + * : Action to perform. | |
| 1434 | + * --- | |
| 1435 | + * options: | |
| 1436 | + * - activate | |
| 1437 | + * - deactivate | |
| 1438 | + * - clear | |
| 1439 | + * --- | |
| 1440 | + * | |
| 1441 | + * ## EXAMPLES | |
| 1442 | + * | |
| 1443 | + * # Enable CAPTCHA. | |
| 1444 | + * $ wp defender captcha activate | |
| 1445 | + * CAPTCHA is activated. | |
| 1446 | + * | |
| 1447 | + * # Disable CAPTCHA. | |
| 1448 | + * $ wp defender captcha deactivate | |
| 1449 | + * CAPTCHA is deactivated. | |
| 1450 | + * | |
| 1451 | + * # Reset all CAPTCHA settings to defaults. | |
| 1452 | + * $ wp defender captcha clear | |
| 1453 | + * CAPTCHA is cleared. | |
| 1454 | + * | |
| 1455 | + * @param mixed $args Command arguments. | |
| 1099 | 1456 | */ |
| 1100 | 1457 | public function captcha( $args ) { |
| 1101 | 1458 | if ( ! is_array( $args ) || array() === $args ) { |
| 1102 | 1459 | WP_CLI::error( 'Invalid command.' ); |
| 1103 | - WP_CLI::runcommand( 'defender captcha --help' ); | |
| 1104 | 1460 | |
| 1105 | 1461 | return; |
| 1106 | 1462 | } |
| 1107 | - $model = wd_di()->get( \WP_Defender\Model\Setting\Captcha::class ); | |
| 1108 | - [ $command ] = $args; | |
| 1463 | + $model = wd_di()->get( \WP_Defender\Model\Setting\Captcha::class ); | |
| 1464 | + [$command] = $args; | |
| 1109 | 1465 | switch ( $command ) { |
| 1110 | 1466 | case 'activate': |
| 1111 | 1467 | if ( true !== $model->enabled ) { |
| 1112 | 1468 | $model->enabled = true; |
| @@ -1118,9 +1474,8 @@ | ||
| 1118 | 1474 | if ( false !== $model->enabled ) { |
| 1119 | 1475 | $model->enabled = false; |
| 1120 | 1476 | $model->save(); |
| 1121 | 1477 | } |
| 1122 | - $model->save(); | |
| 1123 | 1478 | WP_CLI::log( 'CAPTCHA is deactivated.' ); |
| 1124 | 1479 | break; |
| 1125 | 1480 | case 'clear': |
| 1126 | 1481 | $default_values = $model->get_default_values(); |
| @@ -1161,9 +1516,8 @@ | ||
| 1161 | 1516 | WP_CLI::log( 'CAPTCHA is cleared.' ); |
| 1162 | 1517 | break; |
| 1163 | 1518 | default: |
| 1164 | 1519 | WP_CLI::error( sprintf( 'Unknown command %s.', $command ) ); |
| 1165 | - WP_CLI::runcommand( 'defender captcha --help' ); | |
| 1166 | 1520 | break; |
| 1167 | 1521 | } |
| 1168 | 1522 | } |
| 1169 | 1523 | } |