| @@ -284,9 +284,11 @@ | ||
| 284 | 284 | * @return bool |
| 285 | 285 | */ |
| 286 | 286 | private function is_insecure_username( $username ) { |
| 287 | 287 | $username = strtolower( trim( $username ) ); |
| 288 | - $insecure_usernames = $this->options['insecure_usernames'] ?? array(); | |
| 288 | + // Vigilante_Settings::list_or_shipped(): an empty list of a setting with | |
| 289 | + // no field on screen is a leftover, not a choice. See its docblock. | |
| 290 | + $insecure_usernames = $this->get_insecure_usernames(); | |
| 289 | 291 | |
| 290 | 292 | return in_array( $username, array_map( 'strtolower', $insecure_usernames ), true ); |
| 291 | 293 | } |
| 292 | 294 | |
| @@ -988,9 +990,14 @@ | ||
| 988 | 990 | * |
| 989 | 991 | * @return array |
| 990 | 992 | */ |
| 991 | 993 | public function get_insecure_usernames() { |
| 992 | - return $this->options['insecure_usernames'] ?? array(); | |
| 994 | + // The single reader of this list, so what blocks and what the screen | |
| 995 | + // reports can never disagree. | |
| 996 | + return Vigilante_Settings::list_or_shipped( | |
| 997 | + $this->options['insecure_usernames'] ?? array(), | |
| 998 | + array( 'user_security', 'insecure_usernames' ) | |
| 999 | + ); | |
| 993 | 1000 | } |
| 994 | 1001 | |
| 995 | 1002 | /** |
| 996 | 1003 | * Check for existing insecure admin users |
| @@ -1249,22 +1256,27 @@ | ||
| 1249 | 1256 | * @param string $password Password. |
| 1250 | 1257 | * @return WP_User|WP_Error|null |
| 1251 | 1258 | */ |
| 1252 | 1259 | public function check_force_reset_on_login( $user, $username, $password ) { |
| 1253 | - // Resolve the target user. The flag must be evaluated whether the | |
| 1254 | - // credentials matched (WP_User) or not (WP_Error). | |
| 1255 | - if ( $user instanceof WP_User ) { | |
| 1256 | - $login_user = $user; | |
| 1257 | - } else { | |
| 1258 | - $login_user = get_user_by( 'login', $username ); | |
| 1259 | - if ( ! $login_user ) { | |
| 1260 | - $login_user = get_user_by( 'email', $username ); | |
| 1261 | - } | |
| 1260 | + /* | |
| 1261 | + * Only a login that would otherwise have succeeded is turned into this | |
| 1262 | + * rejection. Wrong credentials are left exactly as WordPress reported | |
| 1263 | + * them, and they are counted like any other failed login. | |
| 1264 | + * | |
| 1265 | + * Until 2.11.12 this ran for a WP_Error too, resolving the account from | |
| 1266 | + * the username, and replaced an incorrect_password with the rejection | |
| 1267 | + * below. A rejection of Vigilant's own is not counted towards the brute | |
| 1268 | + * force lockout, so any account with a pending forced reset could be | |
| 1269 | + * guessed at without limit: measured on 17 Sep 2026 against 2.11.11 and | |
| 1270 | + * against the first build of 2.11.12, six wrong passwords in a row, none | |
| 1271 | + * of them counted and no lockout at the end. The message this function | |
| 1272 | + * exists to show belongs to whoever typed the right password. | |
| 1273 | + */ | |
| 1274 | + if ( ! ( $user instanceof WP_User ) ) { | |
| 1275 | + return $user; | |
| 1262 | 1276 | } |
| 1263 | 1277 | |
| 1264 | - if ( ! $login_user ) { | |
| 1265 | - return $user; | |
| 1266 | - } | |
| 1278 | + $login_user = $user; | |
| 1267 | 1279 | |
| 1268 | 1280 | // Check if this user has a pending forced reset. |
| 1269 | 1281 | $force_reset = get_user_meta( $login_user->ID, 'vigilante_force_reset_pending', true ); |
| 1270 | 1282 | if ( ! $force_reset ) { |
| @@ -1270,17 +1282,11 @@ | ||
| 1270 | 1282 | if ( ! $force_reset ) { |
| 1271 | 1283 | return $user; |
| 1272 | 1284 | } |
| 1273 | 1285 | |
| 1274 | - // If credentials were wrong with an error other than incorrect_password | |
| 1275 | - // (e.g. a Vigilant lockout, pending approval), don't shadow it. | |
| 1276 | - if ( is_wp_error( $user ) && ! in_array( 'incorrect_password', $user->get_error_codes(), true ) ) { | |
| 1277 | - return $user; | |
| 1278 | - } | |
| 1286 | + // Not counted towards the brute force lockout: the rejection is | |
| 1287 | + // recognised by its error code (Vigilante_Login_Security::CONTROLLED_REJECTIONS). | |
| 1279 | 1288 | |
| 1280 | - // Skip brute force counter for this controlled rejection. | |
| 1281 | - add_filter( 'vigilante_skip_failed_login_count', '__return_true' ); | |
| 1282 | - | |
| 1283 | 1289 | // Surface the controlled rejection in the activity log so the admin |
| 1284 | 1290 | // can tell apart "user fails login because they typed wrong password" |
| 1285 | 1291 | // from "user fails login because we are forcing a reset". |
| 1286 | 1292 | if ( $this->activity_log ) { |
| @@ -1342,9 +1348,14 @@ | ||
| 1342 | 1348 | return; |
| 1343 | 1349 | } |
| 1344 | 1350 | |
| 1345 | 1351 | $settings = $this->options['registration_approval'] ?? array(); |
| 1346 | - $affected_roles = $settings['affected_roles'] ?? array( 'subscriber' ); | |
| 1352 | + // Vigilante_Settings::list_or_shipped(): an empty list of a setting with | |
| 1353 | + // no field on screen is a leftover, not a choice. See its docblock. | |
| 1354 | + $affected_roles = Vigilante_Settings::list_or_shipped( | |
| 1355 | + $settings['affected_roles'] ?? array(), | |
| 1356 | + array( 'user_security', 'registration_approval', 'affected_roles' ) | |
| 1357 | + ); | |
| 1347 | 1358 | |
| 1348 | 1359 | // Check if user role requires approval |
| 1349 | 1360 | $user_roles = $user->roles; |
| 1350 | 1361 | $needs_approval = array_intersect( $user_roles, $affected_roles ); |
| @@ -1390,11 +1401,8 @@ | ||
| 1390 | 1401 | return $user; |
| 1391 | 1402 | } |
| 1392 | 1403 | |
| 1393 | 1404 | if ( self::is_pending_anywhere( $user->ID ) ) { |
| 1394 | - // Mark this as a controlled rejection (not a brute force attempt) | |
| 1395 | - add_filter( 'vigilante_skip_failed_login_count', '__return_true' ); | |
| 1396 | - | |
| 1397 | 1405 | return new WP_Error( |
| 1398 | 1406 | 'pending_approval', |
| 1399 | 1407 | __( '<strong>Account pending:</strong> Your account is awaiting administrator approval. You will receive an email once approved.', 'vigilante' ) |
| 1400 | 1408 | ); |
| @@ -2143,11 +2151,8 @@ | ||
| 2143 | 2151 | 'warning' |
| 2144 | 2152 | ); |
| 2145 | 2153 | } |
| 2146 | 2154 | |
| 2147 | - // Mark this as a controlled rejection (not a brute force attempt) | |
| 2148 | - add_filter( 'vigilante_skip_failed_login_count', '__return_true' ); | |
| 2149 | - | |
| 2150 | 2155 | return new WP_Error( |
| 2151 | 2156 | 'session_limit_exceeded', |
| 2152 | 2157 | sprintf( |
| 2153 | 2158 | /* translators: %d: Maximum sessions allowed */ |
| @@ -3239,9 +3244,9 @@ | ||
| 3239 | 3244 | |
| 3240 | 3245 | // Check if this user's role requires approval |
| 3241 | 3246 | $needs_approval = false; |
| 3242 | 3247 | if ( ! empty( $registration_approval['enabled'] ) ) { |
| 3243 | - $affected_roles = $registration_approval['affected_roles'] ?? array( 'subscriber' ); | |
| 3248 | + $affected_roles = Vigilante_Settings::list_or_shipped( $registration_approval['affected_roles'] ?? array(), array( 'user_security', 'registration_approval', 'affected_roles' ) ); | |
| 3244 | 3249 | $needs_approval = ! empty( array_intersect( $user->roles, $affected_roles ) ); |
| 3245 | 3250 | } |
| 3246 | 3251 | |
| 3247 | 3252 | // Check if email verification is enabled |
| @@ -3353,9 +3358,9 @@ | ||
| 3353 | 3358 | if ( empty( $registration_approval['enabled'] ) ) { |
| 3354 | 3359 | return false; |
| 3355 | 3360 | } |
| 3356 | 3361 | |
| 3357 | - $affected_roles = $registration_approval['affected_roles'] ?? array( 'subscriber' ); | |
| 3362 | + $affected_roles = Vigilante_Settings::list_or_shipped( $registration_approval['affected_roles'] ?? array(), array( 'user_security', 'registration_approval', 'affected_roles' ) ); | |
| 3358 | 3363 | return ! empty( array_intersect( $user->roles, $affected_roles ) ); |
| 3359 | 3364 | } |
| 3360 | 3365 | |
| 3361 | 3366 | /** |