| @@ -22,95 +22,18 @@ | ||
| 22 | 22 | * AJAX: Apply preset |
| 23 | 23 | */ |
| 24 | 24 | // ajax_apply_preset() is defined in class-admin.php directly (not in this trait) |
| 25 | 25 | |
| 26 | - /** | |
| 27 | - * AJAX: Clear lockouts | |
| 26 | + /* | |
| 27 | + * ajax_clear_lockouts(), ajax_clear_logs(), ajax_run_scan() and | |
| 28 | + * ajax_test_headers() live in class-admin.php. Until 2.11.8 this trait | |
| 29 | + * carried older copies of the four, and PHP runs the method of the class, | |
| 30 | + * so the copies never ran: a fix written into one of them would have looked | |
| 31 | + * applied and changed nothing. Removed after the audit of the admin surface | |
| 32 | + * for 2.11.8 found them. | |
| 28 | 33 | */ |
| 29 | - public function ajax_clear_lockouts() { | |
| 30 | - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); | |
| 31 | 34 | |
| 32 | - if ( ! current_user_can( 'manage_options' ) ) { | |
| 33 | - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) ); | |
| 34 | - } | |
| 35 | - | |
| 36 | - $ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : ''; | |
| 37 | - | |
| 38 | - if ( ! empty( $ip ) ) { | |
| 39 | - // Clear specific IP | |
| 40 | - $result = $this->database->clear_lockout( $ip ); | |
| 41 | - } else { | |
| 42 | - // Clear all | |
| 43 | - $result = $this->database->clear_all_lockouts(); | |
| 44 | - } | |
| 45 | - | |
| 46 | - if ( $result ) { | |
| 47 | - wp_send_json_success( __( 'Lockouts cleared.', 'vigilante' ) ); | |
| 48 | - } else { | |
| 49 | - wp_send_json_error( __( 'Failed to clear lockouts.', 'vigilante' ) ); | |
| 50 | - } | |
| 51 | - } | |
| 52 | - | |
| 53 | 35 | /** |
| 54 | - * AJAX: Clear logs | |
| 55 | - */ | |
| 56 | - public function ajax_clear_logs() { | |
| 57 | - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); | |
| 58 | - | |
| 59 | - if ( ! current_user_can( 'manage_options' ) ) { | |
| 60 | - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) ); | |
| 61 | - } | |
| 62 | - | |
| 63 | - $result = $this->activity_log->clear_all_logs(); | |
| 64 | - | |
| 65 | - if ( $result ) { | |
| 66 | - wp_send_json_success( __( 'Logs cleared.', 'vigilante' ) ); | |
| 67 | - } else { | |
| 68 | - wp_send_json_error( __( 'Failed to clear logs.', 'vigilante' ) ); | |
| 69 | - } | |
| 70 | - } | |
| 71 | - | |
| 72 | - /** | |
| 73 | - * AJAX: Run file integrity scan | |
| 74 | - */ | |
| 75 | - public function ajax_run_scan() { | |
| 76 | - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); | |
| 77 | - | |
| 78 | - if ( ! current_user_can( 'manage_options' ) ) { | |
| 79 | - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) ); | |
| 80 | - } | |
| 81 | - | |
| 82 | - try { | |
| 83 | - if ( ! class_exists( 'Vigilante_File_Integrity' ) ) { | |
| 84 | - require_once VIGILANTE_PLUGIN_DIR . 'includes/class-file-integrity.php'; | |
| 85 | - } | |
| 86 | - | |
| 87 | - if ( ! $this->settings ) { | |
| 88 | - wp_send_json_error( 'Settings not initialized' ); | |
| 89 | - } | |
| 90 | - | |
| 91 | - $activity_log = isset( $this->activity_log ) ? $this->activity_log : null; | |
| 92 | - $database = isset( $this->database ) ? $this->database : null; | |
| 93 | - | |
| 94 | - $file_integrity = new Vigilante_File_Integrity( $this->settings, $database, $activity_log ); | |
| 95 | - $results = $file_integrity->run_scan(); | |
| 96 | - | |
| 97 | - // Save results for display | |
| 98 | - update_option( 'vigilante_last_integrity_scan', time() ); | |
| 99 | - update_option( 'vigilante_last_integrity_results', $results ); | |
| 100 | - | |
| 101 | - wp_send_json_success( array( | |
| 102 | - 'message' => __( 'Scan completed.', 'vigilante' ), | |
| 103 | - 'results' => $results, | |
| 104 | - ) ); | |
| 105 | - } catch ( Exception $e ) { | |
| 106 | - wp_send_json_error( 'Exception: ' . $e->getMessage() ); | |
| 107 | - } catch ( Error $e ) { | |
| 108 | - wp_send_json_error( 'PHP Error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() ); | |
| 109 | - } | |
| 110 | - } | |
| 111 | - | |
| 112 | - /** | |
| 113 | 36 | * AJAX: Approve a critical config file modification |
| 114 | 37 | * |
| 115 | 38 | * Updates the baseline hash for a single critical file (wp-config.php |
| 116 | 39 | * or .htaccess), accepting the current content as legitimate. |
| @@ -258,8 +181,16 @@ | ||
| 258 | 181 | $log->is_ip_blacklisted = ( '' !== $ip_val && in_array( $ip_val, $ip_blacklist, true ) ); |
| 259 | 182 | $log->is_ua_whitelisted = ( '' !== $ua_val && in_array( $ua_val, $ua_whitelist, true ) ); |
| 260 | 183 | $log->is_ua_blacklisted = ( '' !== $ua_val && in_array( $ua_val, $ua_blacklist, true ) ); |
| 261 | 184 | $log->request_uri = Vigilante_Activity_Log::extract_request_uri( $log->extra_data ?? '' ); |
| 185 | + // Same explanation as the first page load: without this, an entry | |
| 186 | + // reached by filtering or paginating would open a popup with less | |
| 187 | + // in it than the same entry opened from the first page. | |
| 188 | + $log->self_guidance = Vigilante_Self_Integrity_Guidance::for_log_event( | |
| 189 | + (string) ( $log->event_action ?? '' ), | |
| 190 | + $log->extra_data ?? '', | |
| 191 | + (string) ( $log->severity ?? 'info' ) | |
| 192 | + ); | |
| 262 | 193 | } |
| 263 | 194 | |
| 264 | 195 | wp_send_json_success( array( |
| 265 | 196 | 'logs' => $logs, |
| @@ -340,8 +271,18 @@ | ||
| 340 | 271 | $removed_from_opposite = true; |
| 341 | 272 | } |
| 342 | 273 | } |
| 343 | 274 | |
| 275 | + // On the main site of a network the whitelists also build the .htaccess | |
| 276 | + // rules every site shares, so a user without network rights cannot put | |
| 277 | + // an entry in them or take one out (2.11.6). | |
| 278 | + $locked = Vigilante_Settings::get_locked_file_settings(); | |
| 279 | + $locked_firewall = ( isset( $locked['firewall'] ) && is_array( $locked['firewall'] ) ) ? $locked['firewall'] : array(); | |
| 280 | + | |
| 281 | + if ( in_array( $option_key, $locked_firewall, true ) || ( $removed_from_opposite && in_array( $opposite_key, $locked_firewall, true ) ) ) { | |
| 282 | + wp_send_json_error( Vigilante_Settings::get_shared_files_notice() ); | |
| 283 | + } | |
| 284 | + | |
| 344 | 285 | wp_cache_delete( Vigilante_Settings::OPTION_NAME, 'options' ); |
| 345 | 286 | update_option( Vigilante_Settings::OPTION_NAME, $all_options ); |
| 346 | 287 | $this->settings->clear_cache(); |
| 347 | 288 | |
| @@ -390,24 +331,8 @@ | ||
| 390 | 331 | wp_send_json_success( $message ); |
| 391 | 332 | } |
| 392 | 333 | |
| 393 | 334 | /** |
| 394 | - * AJAX: Test security headers | |
| 395 | - */ | |
| 396 | - public function ajax_test_headers() { | |
| 397 | - check_ajax_referer( 'vigilante_admin_nonce', 'nonce' ); | |
| 398 | - | |
| 399 | - if ( ! current_user_can( 'manage_options' ) ) { | |
| 400 | - wp_send_json_error( __( 'Permission denied.', 'vigilante' ) ); | |
| 401 | - } | |
| 402 | - | |
| 403 | - $security_headers = new Vigilante_Security_Headers( $this->settings ); | |
| 404 | - $results = $security_headers->test_headers(); | |
| 405 | - | |
| 406 | - wp_send_json_success( $results ); | |
| 407 | - } | |
| 408 | - | |
| 409 | - /** | |
| 410 | 335 | * Sanitize activity log data |
| 411 | 336 | * |
| 412 | 337 | * @param array $data Data to sanitize. |
| 413 | 338 | * @return array |
| @@ -1129,8 +1054,29 @@ | ||
| 1129 | 1054 | if ( ! $user_id ) { |
| 1130 | 1055 | wp_send_json_error( __( 'Invalid user ID.', 'vigilante' ) ); |
| 1131 | 1056 | } |
| 1132 | 1057 | |
| 1058 | + /* | |
| 1059 | + * Permission over that account, which on a network only a network | |
| 1060 | + * administrator has (wp-includes/capabilities.php:75). Same rule the other | |
| 1061 | + * account tools got in 2.10.3, kept here in 2.11.8. | |
| 1062 | + * | |
| 1063 | + * The reason written here until 2.11.10 was that the pending flag is one | |
| 1064 | + * user meta shared by the whole network, and that stopped being true in | |
| 1065 | + * this very release: the flag is per site now and approving clears only | |
| 1066 | + * this site's. The check stays all the same, and deliberately. Approving | |
| 1067 | + * is what lets somebody into a network whose session cookie is valid on | |
| 1068 | + * every site of it, and the queue is shown to a site administrator so they | |
| 1069 | + * can see who is waiting, with the button locked and explained, which is | |
| 1070 | + * how it has behaved since 2.11.8 and what matriz-red-limpieza-2114.sh | |
| 1071 | + * checks. Loosening it is a decision about who may let people into a | |
| 1072 | + * network, not a tidy-up, so it belongs with the rest of the network | |
| 1073 | + * permissions work in 3.1.0 and not in a security release. | |
| 1074 | + */ | |
| 1075 | + if ( ! current_user_can( 'edit_user', $user_id ) ) { | |
| 1076 | + wp_send_json_error( __( 'Permission denied.', 'vigilante' ) ); | |
| 1077 | + } | |
| 1078 | + | |
| 1133 | 1079 | $user_security = new Vigilante_User_Security( $this->settings, $this->activity_log ); |
| 1134 | 1080 | $result = $user_security->approve_user( $user_id, get_current_user_id() ); |
| 1135 | 1081 | |
| 1136 | 1082 | if ( $result ) { |
| @@ -1161,8 +1107,14 @@ | ||
| 1161 | 1107 | $reason = isset( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : ''; |
| 1162 | 1108 | |
| 1163 | 1109 | if ( ! $user_id ) { |
| 1164 | 1110 | wp_send_json_error( __( 'Invalid user ID.', 'vigilante' ) ); |
| 1111 | + } | |
| 1112 | + | |
| 1113 | + // See ajax_approve_user(): the account and its pending flag belong to the | |
| 1114 | + // whole network (2.11.8). | |
| 1115 | + if ( ! current_user_can( 'edit_user', $user_id ) ) { | |
| 1116 | + wp_send_json_error( __( 'Permission denied.', 'vigilante' ) ); | |
| 1165 | 1117 | } |
| 1166 | 1118 | |
| 1167 | 1119 | $user = get_userdata( $user_id ); |
| 1168 | 1120 | $username = $user ? $user->user_login : $user_id; |