PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.11.8
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.11.8
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | includes/class-user-security.php +51 -12 2.9.72.11.8 View file →
@@ -1094,13 +1094,23 @@
1094 1094 public function force_password_reset_bulk( $user_ids, $reset_by_user_id = 0 ) {
1095 1095 $results = array(
1096 1096 'success' => 0,
1097 1097 'failed' => 0,
1098 + 'skipped' => 0,
1098 1099 'emails_sent' => 0,
1099 1100 'total' => count( $user_ids ),
1100 1101 );
1101 1102
1102 1103 foreach ( $user_ids as $user_id ) {
1104 + // The caller only proved it holds manage_options, which on a network
1105 + // every subsite administrator has. Resetting somebody else's password
1106 + // locks them out, so each target is checked one by one. Skipped users
1107 + // are counted apart from real failures.
1108 + if ( ! current_user_can( 'edit_user', $user_id ) ) {
1109 + $results['skipped']++;
1110 + continue;
1111 + }
1112 +
1103 1113 $result = $this->force_password_reset( $user_id, $reset_by_user_id );
1104 1114
1105 1115 if ( $result['success'] ) {
1106 1116 $results['success']++;
@@ -1383,9 +1393,9 @@
1383 1393 $count,
1384 1394 'vigilante'
1385 1395 ) ),
1386 1396 absint( $count ),
1387 - '<a href="' . esc_url( admin_url( 'admin.php?page=vigilante&tab=users' ) ) . '">' . esc_html__( 'Review in Vigilant', 'vigilante' ) . '</a>'
1397 + '<a href="' . esc_url( admin_url( 'admin.php?page=vigilante&tab=users#vigilante-section-users-pending' ) ) . '">' . esc_html__( 'Review in Vigilant', 'vigilante' ) . '</a>'
1388 1398 );
1389 1399 ?>
1390 1400 </p>
1391 1401 </div>
@@ -1417,8 +1427,14 @@
1417 1427 * @param int $approved_by Admin user ID who approved.
1418 1428 * @return bool
1419 1429 */
1420 1430 public function approve_user( $user_id, $approved_by = 0 ) {
1431 + // Same reasoning as reject_user(): approving an account that never asked
1432 + // for approval is a no-op that reports success and writes misleading meta.
1433 + if ( ! get_user_meta( $user_id, 'vigilante_pending_approval', true ) ) {
1434 + return false;
1435 + }
1436 +
1421 1437 $user = get_userdata( $user_id );
1422 1438 if ( ! $user ) {
1423 1439 return false;
1424 1440 }
@@ -1464,8 +1480,16 @@
1464 1480 if ( ! $user ) {
1465 1481 return false;
1466 1482 }
1467 1483
1484 + // Only an account actually waiting for approval may be rejected. Without
1485 + // this the handler deletes any user id it is given, and wp_delete_user()
1486 + // with no reassignment takes their posts with them, skipping the dialog
1487 + // core always shows. Deleting a member is the Users screen's job.
1488 + if ( ! get_user_meta( $user_id, 'vigilante_pending_approval', true ) ) {
1489 + return false;
1490 + }
1491 +
1468 1492 // Log before deletion
1469 1493 if ( $this->activity_log ) {
1470 1494 $admin = $rejected_by ? get_userdata( $rejected_by ) : null;
1471 1495 $this->activity_log->log(
@@ -1509,9 +1533,9 @@
1509 1533 __( '[%s] New user registration pending approval', 'vigilante' ),
1510 1534 $site_name
1511 1535 );
1512 1536
1513 - $approve_url = admin_url( 'admin.php?page=vigilante&tab=users' );
1537 + $approve_url = admin_url( 'admin.php?page=vigilante&tab=users#vigilante-section-users-pending' );
1514 1538
1515 1539 $body = Vigilante_Email_Template::p( __( 'A new user has registered and is awaiting your approval.', 'vigilante' ) );
1516 1540 $body .= Vigilante_Email_Template::data_table( array(
1517 1541 __( 'Username', 'vigilante' ) => $user->user_login,
@@ -2027,9 +2051,15 @@
2027 2051
2028 2052 // Honor both affected_roles AND the per-user exclusion list, and
2029 2053 // clear stale flags if the user no longer matches the rules.
2030 2054 if ( ! $this->is_password_expiration_applicable( $user_id ) ) {
2031 - if ( get_user_meta( $user_id, 'vigilante_must_change_password', true ) ) {
2055 + // Only on a single site, for the same reason as in
2056 + // force_password_change_redirect(): on a network the flag belongs to
2057 + // the account, and this site's policy says nothing about the site
2058 + // that set it. The 2.11.8 fix only covered that method, and this
2059 + // notice cleared the flag anyway on the next admin page; found by
2060 + // the cross review of 2.11.8.
2061 + if ( ! is_multisite() && get_user_meta( $user_id, 'vigilante_must_change_password', true ) ) {
2032 2062 delete_user_meta( $user_id, 'vigilante_must_change_password' );
2033 2063 }
2034 2064 return;
2035 2065 }
@@ -2122,9 +2152,16 @@
2122 2152 // excluded list after the flag was set. Without this check the flag
2123 2153 // outlives the configuration change and locks the user in a redirect
2124 2154 // loop into profile.php.
2125 2155 if ( ! $this->is_password_expiration_applicable( $user_id ) ) {
2126 - delete_user_meta( $user_id, 'vigilante_must_change_password' );
2156 + // On a network the flag is a user meta that every site shares, and the
2157 + // policy just checked is only this site's: another site may have set
2158 + // it, and clearing it here let a user skip that site's forced change
2159 + // by visiting any other dashboard. It is only cleared on a single
2160 + // site (2.11.8); on a network the user is just not redirected here.
2161 + if ( ! is_multisite() ) {
2162 + delete_user_meta( $user_id, 'vigilante_must_change_password' );
2163 + }
2127 2164 return;
2128 2165 }
2129 2166
2130 2167 wp_safe_redirect( admin_url( 'profile.php#password' ) );
@@ -2669,20 +2706,22 @@
2669 2706 wp_safe_redirect( add_query_arg( 'vigilante_message', 'invalid', wp_login_url() ) );
2670 2707 exit;
2671 2708 }
2672 2709
2673 - $stored_hash = get_user_meta( $user_id, 'vigilante_verification_token', true );
2674 - $expires = get_user_meta( $user_id, 'vigilante_verification_expires', true );
2710 + $stored_hash = (string) get_user_meta( $user_id, 'vigilante_verification_token', true );
2711 + $expires = (int) get_user_meta( $user_id, 'vigilante_verification_expires', true );
2675 2712
2676 - // Check expiration
2677 - if ( time() > $expires ) {
2678 - wp_safe_redirect( add_query_arg( 'vigilante_message', 'expired', wp_login_url() ) );
2713 + // The token first. Checking the expiry before it answered "expired" for
2714 + // any account with no verification pending and "invalid" for one waiting,
2715 + // so a wrong link revealed which user ids were waiting (2.11.8). Only the
2716 + // holder of the right token learns that it expired.
2717 + if ( '' === $stored_hash || ! hash_equals( $stored_hash, wp_hash( $token ) ) ) {
2718 + wp_safe_redirect( add_query_arg( 'vigilante_message', 'invalid', wp_login_url() ) );
2679 2719 exit;
2680 2720 }
2681 2721
2682 - // Verify token
2683 - if ( ! hash_equals( $stored_hash, wp_hash( $token ) ) ) {
2684 - wp_safe_redirect( add_query_arg( 'vigilante_message', 'invalid', wp_login_url() ) );
2722 + if ( time() > $expires ) {
2723 + wp_safe_redirect( add_query_arg( 'vigilante_message', 'expired', wp_login_url() ) );
2685 2724 exit;
2686 2725 }
2687 2726
2688 2727 // Mark as verified