| @@ -360,22 +360,20 @@ | ||
| 360 | 360 | if ( ! is_user_logged_in() ) { |
| 361 | 361 | return false; |
| 362 | 362 | } |
| 363 | 363 | |
| 364 | - if ( empty( $_POST['uwp_reset_nonce'] ) || ! wp_verify_nonce( $_POST['uwp_reset_nonce'], 'uwp_reset_nonce_' . $type ) ) { | |
| 365 | - return; | |
| 366 | - } | |
| 367 | - | |
| 368 | 364 | if ( is_admin() && defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) { |
| 369 | 365 | $user_id = get_current_user_id(); |
| 370 | - // If is another user's profile page | |
| 371 | - } elseif ( is_admin() && ! empty( $_GET['user_id'] ) && is_numeric( $_GET['user_id'] ) ) { | |
| 366 | + } elseif ( is_admin() && current_user_can( 'manage_options' ) && ! empty( $_GET['user_id'] ) && is_numeric( $_GET['user_id'] ) ) { | |
| 372 | 367 | $user_id = absint( $_GET['user_id'] ); |
| 373 | - // Otherwise something is wrong. | |
| 374 | 368 | } else { |
| 375 | 369 | $user_id = get_current_user_id(); |
| 376 | 370 | } |
| 377 | 371 | |
| 372 | + if ( empty( $_POST['uwp_reset_nonce'] ) || ! wp_verify_nonce( $_POST['uwp_reset_nonce'], 'uwp_reset_nonce_' . $type . '_' . $user_id ) ) { | |
| 373 | + return; | |
| 374 | + } | |
| 375 | + | |
| 378 | 376 | $errors = new WP_Error(); |
| 379 | 377 | if ( empty( $user_id ) ) { |
| 380 | 378 | $errors->add( 'something_wrong', __( 'Something went wrong. Please try again.', 'userswp' ) ); |
| 381 | 379 | } |
| @@ -1177,8 +1175,11 @@ | ||
| 1177 | 1175 | global $wp2fa; |
| 1178 | 1176 | if ( wp_doing_ajax() && isset( $wp2fa ) && ! empty( $wp2fa ) ) { |
| 1179 | 1177 | remove_action( 'wp_login', array( $wp2fa->login, 'wp_login' ), 20 ); |
| 1180 | 1178 | } |
| 1179 | + if ( wp_doing_ajax() && class_exists( '\WP2FA\Authenticator\Login' ) ) { | |
| 1180 | + remove_action( 'wp_login', array( 'WP2FA\Authenticator\Login', 'wp_login' ), 20 ); | |
| 1181 | + } | |
| 1181 | 1182 | |
| 1182 | 1183 | $user = wp_signon( |
| 1183 | 1184 | array( |
| 1184 | 1185 | 'user_login' => $result['username'], |
| @@ -1187,10 +1188,14 @@ | ||
| 1187 | 1188 | ) |
| 1188 | 1189 | ); |
| 1189 | 1190 | |
| 1190 | 1191 | add_action( 'authenticate', 'gglcptch_login_check', 21, 1 ); |
| 1192 | + if ( wp_doing_ajax() && class_exists( '\WP2FA\Authenticator\Login' ) ) { | |
| 1193 | + add_action( 'wp_login', array( 'WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 ); | |
| 1194 | + } | |
| 1191 | 1195 | |
| 1192 | - if ( wp_doing_ajax() && ! is_wp_error( $user ) && isset( $wp2fa ) && ! empty( $wp2fa ) ) { | |
| 1196 | + $wp2fa_available = ( isset( $wp2fa ) && ! empty( $wp2fa ) ) || class_exists( '\WP2FA\Authenticator\Login' ); | |
| 1197 | + if ( wp_doing_ajax() && ! is_wp_error( $user ) && $wp2fa_available ) { | |
| 1193 | 1198 | |
| 1194 | 1199 | $two_fa = $this->check_2fa( $user ); |
| 1195 | 1200 | if ( isset( $two_fa ) && ! empty( $two_fa ) ) { |
| 1196 | 1201 | if ( is_wp_error( $two_fa ) ) { |
| @@ -1211,8 +1216,20 @@ | ||
| 1211 | 1216 | } |
| 1212 | 1217 | } |
| 1213 | 1218 | } |
| 1214 | 1219 | |
| 1220 | + if ( wp_doing_ajax() && is_wp_error( $user ) && $this->wordfence_2fa_available() ) { | |
| 1221 | + $wfls_2fa = $this->check_wordfence_2fa( $user, $result ); | |
| 1222 | + if ( ! empty( $wfls_2fa ) ) { | |
| 1223 | + wp_send_json_success( | |
| 1224 | + array( | |
| 1225 | + 'html' => $wfls_2fa, | |
| 1226 | + 'is_2fa' => true, | |
| 1227 | + ) | |
| 1228 | + ); | |
| 1229 | + } | |
| 1230 | + } | |
| 1231 | + | |
| 1215 | 1232 | if ( is_wp_error( $user ) ) { |
| 1216 | 1233 | $message = aui()->alert( |
| 1217 | 1234 | array( |
| 1218 | 1235 | 'type' => 'error', |
| @@ -1280,9 +1297,12 @@ | ||
| 1280 | 1297 | |
| 1281 | 1298 | return $errors; |
| 1282 | 1299 | } |
| 1283 | 1300 | |
| 1284 | - $provider = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user ); | |
| 1301 | + $provider = $this->get_wp2fa_provider_for_user( $user ); | |
| 1302 | + if ( empty( $provider ) ) { | |
| 1303 | + return; | |
| 1304 | + } | |
| 1285 | 1305 | |
| 1286 | 1306 | ob_start(); |
| 1287 | 1307 | ?> |
| 1288 | 1308 | |
| @@ -1333,9 +1353,9 @@ | ||
| 1333 | 1353 | echo aui()->input( |
| 1334 | 1354 | array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 1335 | 1355 | 'type' => 'tel', |
| 1336 | 1356 | 'id' => 'authcode', |
| 1337 | - 'name' => 'wp-2fa-email-code', | |
| 1357 | + 'name' => 'authcode', | |
| 1338 | 1358 | 'placeholder' => esc_attr__( 'Verification Code', 'userswp' ), |
| 1339 | 1359 | 'value' => '', |
| 1340 | 1360 | 'label' => esc_html__( 'Verification Code', 'userswp' ), |
| 1341 | 1361 | 'extra_attributes' => array( |
| @@ -1370,9 +1390,9 @@ | ||
| 1370 | 1390 | </form> |
| 1371 | 1391 | </div> |
| 1372 | 1392 | |
| 1373 | 1393 | <?php |
| 1374 | - $codes_remaining = \WP2FA\Authenticator\Backup_Codes::codes_remaining_for_user( $user ); | |
| 1394 | + $codes_remaining = $this->get_wp2fa_backup_codes_remaining( $user ); | |
| 1375 | 1395 | if ( isset( $codes_remaining ) && $codes_remaining > 0 ) { |
| 1376 | 1396 | ?> |
| 1377 | 1397 | <div class="uwp-2fa-methods-wrap" style="display:none;"> |
| 1378 | 1398 | <form name="validate_2fa_backup_codes_form" id="validate_2fa_backup_codes_form" |
| @@ -1428,9 +1448,247 @@ | ||
| 1428 | 1448 | |
| 1429 | 1449 | return ob_get_clean(); |
| 1430 | 1450 | } |
| 1431 | 1451 | |
| 1452 | + /** | |
| 1453 | + * Checks if the Wordfence Login Security module (2FA) is available. | |
| 1454 | + * | |
| 1455 | + * @since 1.2.5 | |
| 1456 | + * @package userswp | |
| 1457 | + * | |
| 1458 | + * @return bool | |
| 1459 | + */ | |
| 1460 | + public function wordfence_2fa_available() { | |
| 1461 | + return class_exists( '\WordfenceLS\Controller_Users' ) && class_exists( '\WordfenceLS\Controller_TOTP' ); | |
| 1462 | + } | |
| 1463 | + | |
| 1464 | + /** | |
| 1465 | + * Checks whether Wordfence's 2FA requires a verification code for the | |
| 1466 | + * failed login attempt and, if so, returns the markup for the code entry form. | |
| 1467 | + * | |
| 1468 | + * @since 1.2.5 | |
| 1469 | + * @package userswp | |
| 1470 | + * | |
| 1471 | + * @param WP_Error $error The error returned by wp_signon(). | |
| 1472 | + * @param array $result The validated login fields (username/password). | |
| 1473 | + * | |
| 1474 | + * @return string|void The 2FA form markup, or nothing if not applicable. | |
| 1475 | + */ | |
| 1476 | + public function check_wordfence_2fa( $error, $result ) { | |
| 1477 | + if ( 1 == uwp_get_option( 'disable_wordfence_2fa' ) ) { | |
| 1478 | + return; | |
| 1479 | + } | |
| 1480 | + | |
| 1481 | + if ( ! $this->wordfence_2fa_available() ) { | |
| 1482 | + return; | |
| 1483 | + } | |
| 1484 | + | |
| 1485 | + if ( ! is_wp_error( $error ) || 'wfls_twofactor_required' !== $error->get_error_code() ) { | |
| 1486 | + return; | |
| 1487 | + } | |
| 1488 | + | |
| 1489 | + $username = ! empty( $result['username'] ) ? $result['username'] : ''; | |
| 1490 | + if ( empty( $username ) ) { | |
| 1491 | + return; | |
| 1492 | + } | |
| 1493 | + | |
| 1494 | + $user = is_email( $username ) ? get_user_by( 'email', $username ) : get_user_by( 'login', $username ); | |
| 1495 | + if ( ! $user ) { | |
| 1496 | + return; | |
| 1497 | + } | |
| 1498 | + | |
| 1499 | + if ( ! \WordfenceLS\Controller_Users::shared()->has_2fa_active( $user ) ) { | |
| 1500 | + return; | |
| 1501 | + } | |
| 1502 | + | |
| 1503 | + if ( \WordfenceLS\Controller_Users::shared()->has_remembered_2fa( $user ) ) { | |
| 1504 | + return; | |
| 1505 | + } | |
| 1506 | + | |
| 1507 | + $login_nonce = wp_create_nonce( 'uwp-wfls-2fa-' . $user->ID ); | |
| 1508 | + | |
| 1509 | + ob_start(); | |
| 1510 | + ?> | |
| 1511 | + | |
| 1512 | + <div class="uwp-2fa-methods-wrap"> | |
| 1513 | + <form name="validate_2fa_form" id="validate_2fa_form" class="validate_2fa_form" action="" method="post" | |
| 1514 | + autocomplete="off"> | |
| 1515 | + <input type="hidden" name="provider" id="provider" value="wordfence"/> | |
| 1516 | + <input type="hidden" name="uwp-auth-id" id="uwp-auth-id" value="<?php echo esc_attr( $user->ID ); ?>"/> | |
| 1517 | + <input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" | |
| 1518 | + value="<?php echo esc_attr( $login_nonce ); ?>"/> | |
| 1519 | + | |
| 1520 | + <p><?php esc_html_e( 'Please enter the authentication code from your two-factor authentication app, or a recovery code, to login:', 'userswp' ); ?></p> | |
| 1521 | + | |
| 1522 | + <?php | |
| 1523 | + echo aui()->input( | |
| 1524 | + array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | |
| 1525 | + 'type' => 'text', | |
| 1526 | + 'id' => 'authcode', | |
| 1527 | + 'name' => 'authcode', | |
| 1528 | + 'placeholder' => esc_attr__( 'Authentication Code', 'userswp' ), | |
| 1529 | + 'value' => '', | |
| 1530 | + 'label' => esc_html__( 'Authentication Code', 'userswp' ), | |
| 1531 | + 'extra_attributes' => array( | |
| 1532 | + 'autocomplete' => 'one-time-code', | |
| 1533 | + ), | |
| 1534 | + ) | |
| 1535 | + ); | |
| 1536 | + | |
| 1537 | + echo aui()->button( | |
| 1538 | + array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | |
| 1539 | + 'type' => 'submit', | |
| 1540 | + 'class' => 'btn btn-primary btn-block text-uppercase uwp-2fa-submit', | |
| 1541 | + 'name' => 'submit', | |
| 1542 | + 'icon' => '', | |
| 1543 | + 'content' => esc_html__( 'Log In', 'userswp' ), | |
| 1544 | + ) | |
| 1545 | + ); | |
| 1546 | + ?> | |
| 1547 | + </form> | |
| 1548 | + </div> | |
| 1549 | + | |
| 1550 | + <?php | |
| 1551 | + return ob_get_clean(); | |
| 1552 | + } | |
| 1553 | + | |
| 1554 | + public function get_wp2fa_provider_for_user( $user ) { | |
| 1555 | + if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'get_available_providers_for_user' ) ) { | |
| 1556 | + $provider = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user ); | |
| 1557 | + if ( is_array( $provider ) ) { | |
| 1558 | + $provider = key( $provider ); | |
| 1559 | + } | |
| 1560 | + | |
| 1561 | + return $provider; | |
| 1562 | + } | |
| 1563 | + | |
| 1564 | + if ( class_exists( '\WP2FA\Admin\Helpers\User_Helper' ) && method_exists( '\WP2FA\Admin\Helpers\User_Helper', 'get_enabled_method_for_user' ) ) { | |
| 1565 | + return \WP2FA\Admin\Helpers\User_Helper::get_enabled_method_for_user( $user ); | |
| 1566 | + } | |
| 1567 | + | |
| 1568 | + return ''; | |
| 1569 | + } | |
| 1570 | + | |
| 1571 | + public function get_wp2fa_backup_codes_remaining( $user ) { | |
| 1572 | + if ( class_exists( '\WP2FA\Methods\Backup_Codes' ) && method_exists( '\WP2FA\Methods\Backup_Codes', 'codes_remaining_for_user' ) ) { | |
| 1573 | + return \WP2FA\Methods\Backup_Codes::codes_remaining_for_user( $user ); | |
| 1574 | + } | |
| 1575 | + | |
| 1576 | + if ( class_exists( '\WP2FA\Authenticator\Backup_Codes' ) && method_exists( '\WP2FA\Authenticator\Backup_Codes', 'codes_remaining_for_user' ) ) { | |
| 1577 | + return \WP2FA\Authenticator\Backup_Codes::codes_remaining_for_user( $user ); | |
| 1578 | + } | |
| 1579 | + | |
| 1580 | + return 0; | |
| 1581 | + } | |
| 1582 | + | |
| 1583 | + public function validate_wp2fa_totp_authentication( $user ) { | |
| 1584 | + if ( class_exists( '\WP2FA\Methods\TOTP' ) && method_exists( '\WP2FA\Methods\TOTP', 'validate_totp_authentication' ) ) { | |
| 1585 | + return \WP2FA\Methods\TOTP::validate_totp_authentication( $user ); | |
| 1586 | + } | |
| 1587 | + | |
| 1588 | + if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'validate_totp_authentication' ) ) { | |
| 1589 | + return \WP2FA\Authenticator\Login::validate_totp_authentication( $user ); | |
| 1590 | + } | |
| 1591 | + | |
| 1592 | + return false; | |
| 1593 | + } | |
| 1594 | + | |
| 1595 | + public function validate_wp2fa_email_authentication( $user ) { | |
| 1596 | + if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'validate_email_authentication' ) ) { | |
| 1597 | + return \WP2FA\Authenticator\Login::validate_email_authentication( $user ); | |
| 1598 | + } | |
| 1599 | + | |
| 1600 | + if ( class_exists( '\WP2FA\Authenticator\Authentication' ) && method_exists( '\WP2FA\Authenticator\Authentication', 'validate_token' ) && isset( $_REQUEST['authcode'] ) ) { | |
| 1601 | + return \WP2FA\Authenticator\Authentication::validate_token( $user, sanitize_text_field( wp_unslash( $_REQUEST['authcode'] ) ) ); | |
| 1602 | + } | |
| 1603 | + | |
| 1604 | + return false; | |
| 1605 | + } | |
| 1606 | + | |
| 1607 | + public function validate_wp2fa_backup_codes( $user ) { | |
| 1608 | + if ( class_exists( '\WP2FA\Methods\Backup_Codes' ) && method_exists( '\WP2FA\Methods\Backup_Codes', 'validate_backup_codes' ) ) { | |
| 1609 | + return \WP2FA\Methods\Backup_Codes::validate_backup_codes( $user ); | |
| 1610 | + } | |
| 1611 | + | |
| 1612 | + if ( class_exists( '\WP2FA\Authenticator\Backup_Codes' ) && method_exists( '\WP2FA\Authenticator\Backup_Codes', 'validate_backup_codes' ) ) { | |
| 1613 | + return \WP2FA\Authenticator\Backup_Codes::validate_backup_codes( $user ); | |
| 1614 | + } | |
| 1615 | + | |
| 1616 | + return false; | |
| 1617 | + } | |
| 1618 | + | |
| 1619 | + /** | |
| 1620 | + * Validates the Wordfence 2FA code submitted from the uwp-2fa form and, | |
| 1621 | + * if valid, completes the login by setting the auth cookie. | |
| 1622 | + * | |
| 1623 | + * @since 1.2.5 | |
| 1624 | + * @package userswp | |
| 1625 | + * | |
| 1626 | + * @param WP_User $user The user attempting to complete 2FA login. | |
| 1627 | + * | |
| 1628 | + * @return void | |
| 1629 | + */ | |
| 1630 | + public function process_login_wordfence_2fa( $user ) { | |
| 1631 | + if ( ! $this->wordfence_2fa_available() ) { | |
| 1632 | + $message = aui()->alert( | |
| 1633 | + array( | |
| 1634 | + 'type' => 'error', | |
| 1635 | + 'content' => __( 'Invalid request! Please try again.', 'userswp' ), | |
| 1636 | + ) | |
| 1637 | + ); | |
| 1638 | + | |
| 1639 | + wp_send_json_error( array( 'message' => $message ) ); | |
| 1640 | + } | |
| 1641 | + | |
| 1642 | + $nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : ''; | |
| 1643 | + | |
| 1644 | + if ( ! wp_verify_nonce( $nonce, 'uwp-wfls-2fa-' . $user->ID ) ) { | |
| 1645 | + $message = aui()->alert( | |
| 1646 | + array( | |
| 1647 | + 'type' => 'error', | |
| 1648 | + 'content' => __( 'Invalid request! Please try again.', 'userswp' ), | |
| 1649 | + ) | |
| 1650 | + ); | |
| 1651 | + | |
| 1652 | + wp_send_json_error( array( 'message' => $message ) ); | |
| 1653 | + } | |
| 1654 | + | |
| 1655 | + $code = isset( $_POST['authcode'] ) ? sanitize_text_field( wp_unslash( $_POST['authcode'] ) ) : ''; | |
| 1656 | + | |
| 1657 | + if ( empty( $code ) || true !== \WordfenceLS\Controller_TOTP::shared()->validate_2fa( $user, $code ) ) { | |
| 1658 | + do_action( 'wp_login_failed', $user->user_login ); | |
| 1659 | + | |
| 1660 | + $message = aui()->alert( | |
| 1661 | + array( | |
| 1662 | + 'type' => 'error', | |
| 1663 | + 'content' => __( 'Invalid verification code.', 'userswp' ), | |
| 1664 | + ) | |
| 1665 | + ); | |
| 1666 | + | |
| 1667 | + wp_send_json_error( array( 'message' => $message ) ); | |
| 1668 | + } | |
| 1669 | + | |
| 1670 | + $remember = ( isset( $_REQUEST['rememberme'] ) ) ? filter_var( $_REQUEST['rememberme'], FILTER_VALIDATE_BOOLEAN ) : false; | |
| 1671 | + | |
| 1672 | + // Complete the login the same way wp_signon() would have, now that 2FA has been verified. | |
| 1673 | + wp_set_auth_cookie( $user->ID, $remember ); | |
| 1674 | + wp_set_current_user( $user->ID ); | |
| 1675 | + | |
| 1676 | + do_action( 'wp_login', $user->user_login, $user ); | |
| 1677 | + | |
| 1678 | + $message = aui()->alert( | |
| 1679 | + array( | |
| 1680 | + 'type' => 'success', | |
| 1681 | + 'content' => __( 'Validation successful. Redirecting...', 'userswp' ), | |
| 1682 | + ) | |
| 1683 | + ); | |
| 1684 | + | |
| 1685 | + wp_send_json_success( array( 'message' => $message ) ); | |
| 1686 | + } | |
| 1687 | + | |
| 1432 | 1688 | public function process_login_2fa() { |
| 1689 | + global $wp2fa; | |
| 1690 | + | |
| 1433 | 1691 | if ( ! isset( $_POST['uwp-auth-id'], $_POST['wp-auth-nonce'] ) ) { |
| 1434 | 1692 | return; |
| 1435 | 1693 | } |
| 1436 | 1694 | |
| @@ -1435,46 +1693,70 @@ | ||
| 1435 | 1693 | } |
| 1436 | 1694 | |
| 1437 | 1695 | $auth_id = (int) $_POST['uwp-auth-id']; |
| 1438 | 1696 | $user = get_userdata( $auth_id ); |
| 1697 | + | |
| 1439 | 1698 | if ( ! $user ) { |
| 1440 | 1699 | $message = aui()->alert( |
| 1441 | - array( | |
| 1700 | + array( | |
| 1442 | 1701 | 'type' => 'error', |
| 1443 | 1702 | 'content' => __( 'Invalid user data. Please try again.', 'userswp' ), |
| 1444 | - ) | |
| 1703 | + ) | |
| 1445 | 1704 | ); |
| 1446 | 1705 | |
| 1447 | 1706 | wp_send_json_error( array( 'message' => $message ) ); |
| 1448 | 1707 | } |
| 1449 | 1708 | |
| 1450 | - global $wp2fa; | |
| 1709 | + if ( isset( $_POST['provider'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 1710 | + $provider = sanitize_textarea_field( wp_unslash( $_POST['provider'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 1711 | + } else { | |
| 1712 | + $provider = ''; | |
| 1713 | + } | |
| 1451 | 1714 | |
| 1715 | + if ( 'wordfence' === $provider ) { | |
| 1716 | + $this->process_login_wordfence_2fa( $user ); | |
| 1717 | + | |
| 1718 | + return; | |
| 1719 | + } | |
| 1720 | + | |
| 1452 | 1721 | $nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : ''; |
| 1722 | + | |
| 1453 | 1723 | if ( true !== \WP2FA\Authenticator\Login::verify_login_nonce( $user->ID, $nonce ) ) { |
| 1454 | - | |
| 1455 | 1724 | $message = aui()->alert( |
| 1456 | - array( | |
| 1725 | + array( | |
| 1457 | 1726 | 'type' => 'error', |
| 1458 | 1727 | 'content' => __( 'Invalid request! Please try again.', 'userswp' ), |
| 1459 | - ) | |
| 1728 | + ) | |
| 1460 | 1729 | ); |
| 1461 | 1730 | |
| 1462 | 1731 | wp_send_json_error( array( 'message' => $message ) ); |
| 1463 | 1732 | } |
| 1464 | 1733 | |
| 1465 | - if ( isset( $_POST['provider'] ) ) { | |
| 1466 | - $provider = sanitize_textarea_field( wp_unslash( $_POST['provider'] ) ); | |
| 1467 | - $providers = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user ); | |
| 1468 | - if ( isset( $providers[ $provider ] ) ) { | |
| 1469 | - $provider = $providers[ $provider ]; | |
| 1470 | - } elseif ( isset( $provider ) ) { | |
| 1471 | - $provider = $provider; | |
| 1472 | - } else { | |
| 1473 | - $provider = $provider; | |
| 1734 | + $error = ''; | |
| 1735 | + | |
| 1736 | + try { | |
| 1737 | + $is_enabled = \WP2FA\Admin\Controllers\Settings::is_provider_enabled_for_role( \WP2FA\Admin\Helpers\User_Helper::get_user_role( $user ), $provider ); | |
| 1738 | + | |
| 1739 | + if ( ! $is_enabled ) { | |
| 1740 | + $error = __( 'Invalid 2FA provider for user.', 'userswp' ); | |
| 1474 | 1741 | } |
| 1742 | + } catch ( \Exception $e ) { | |
| 1743 | + $error = $e->getMessage(); | |
| 1475 | 1744 | } |
| 1476 | 1745 | |
| 1746 | + if ( $error ) { | |
| 1747 | + do_action( 'wp_login_failed', $user->user_login ); | |
| 1748 | + | |
| 1749 | + $message = aui()->alert( | |
| 1750 | + array( | |
| 1751 | + 'type' => 'error', | |
| 1752 | + 'content' => $error | |
| 1753 | + ) | |
| 1754 | + ); | |
| 1755 | + | |
| 1756 | + wp_send_json_error( array( 'message' => $message ) ); | |
| 1757 | + } | |
| 1758 | + | |
| 1477 | 1759 | // If this is an email login, or if the user failed validation previously, lets send the code to the user. |
| 1478 | 1760 | if ( 'email' === $provider && true !== \WP2FA\Authenticator\Login::pre_process_email_authentication( $user ) ) { |
| 1479 | 1761 | |
| 1480 | 1762 | } |
| @@ -1479,17 +1761,16 @@ | ||
| 1479 | 1761 | |
| 1480 | 1762 | } |
| 1481 | 1763 | |
| 1482 | 1764 | // Validate TOTP. |
| 1483 | - if ( 'totp' === $provider && true !== \WP2FA\Authenticator\Login::validate_totp_authentication( $user ) ) { | |
| 1484 | - | |
| 1765 | + if ( 'totp' === $provider && true !== $this->validate_wp2fa_totp_authentication( $user ) ) { | |
| 1485 | 1766 | do_action( 'wp_login_failed', $user->user_login ); |
| 1486 | 1767 | |
| 1487 | 1768 | $message = aui()->alert( |
| 1488 | - array( | |
| 1769 | + array( | |
| 1489 | 1770 | 'type' => 'error', |
| 1490 | 1771 | 'content' => __( 'Invalid verification code.', 'userswp' ), |
| 1491 | - ) | |
| 1772 | + ) | |
| 1492 | 1773 | ); |
| 1493 | 1774 | |
| 1494 | 1775 | wp_send_json_error( array( 'message' => $message ) ); |
| 1495 | 1776 | } |
| @@ -1494,27 +1775,26 @@ | ||
| 1494 | 1775 | wp_send_json_error( array( 'message' => $message ) ); |
| 1495 | 1776 | } |
| 1496 | 1777 | |
| 1497 | 1778 | // Validate Email. |
| 1498 | - if ( 'email' === $provider && true !== \WP2FA\Authenticator\Login::validate_email_authentication( $user ) ) { | |
| 1499 | - | |
| 1779 | + if ( 'email' === $provider && true !== $this->validate_wp2fa_email_authentication( $user ) ) { | |
| 1500 | 1780 | do_action( 'wp_login_failed', $user->user_login ); |
| 1501 | 1781 | |
| 1502 | 1782 | if ( isset( $_REQUEST['wp-2fa-email-code-resend'] ) && 1 == $_REQUEST['wp-2fa-email-code-resend'] ) { |
| 1503 | 1783 | $message = aui()->alert( |
| 1504 | - array( | |
| 1784 | + array( | |
| 1505 | 1785 | 'type' => 'info', |
| 1506 | 1786 | 'content' => __( 'A new code has been sent.', 'userswp' ), |
| 1507 | - ) | |
| 1787 | + ) | |
| 1508 | 1788 | ); |
| 1509 | 1789 | |
| 1510 | 1790 | wp_send_json_error( array( 'message' => $message ) ); |
| 1511 | 1791 | } else { |
| 1512 | 1792 | $message = aui()->alert( |
| 1513 | - array( | |
| 1793 | + array( | |
| 1514 | 1794 | 'type' => 'error', |
| 1515 | 1795 | 'content' => __( 'Invalid verification code.', 'userswp' ), |
| 1516 | - ) | |
| 1796 | + ) | |
| 1517 | 1797 | ); |
| 1518 | 1798 | |
| 1519 | 1799 | wp_send_json_error( array( 'message' => $message ) ); |
| 1520 | 1800 | } |
| @@ -1520,17 +1800,16 @@ | ||
| 1520 | 1800 | } |
| 1521 | 1801 | } |
| 1522 | 1802 | |
| 1523 | 1803 | // Backup Codes. |
| 1524 | - if ( 'backup_codes' === $provider && true !== \WP2FA\Authenticator\Login::validate_backup_codes( $user ) ) { | |
| 1525 | - | |
| 1804 | + if ( 'backup_codes' === $provider && true !== $this->validate_wp2fa_backup_codes( $user ) ) { | |
| 1526 | 1805 | do_action( 'wp_login_failed', $user->user_login ); |
| 1527 | 1806 | |
| 1528 | 1807 | $message = aui()->alert( |
| 1529 | - array( | |
| 1808 | + array( | |
| 1530 | 1809 | 'type' => 'error', |
| 1531 | 1810 | 'content' => __( 'Invalid backup code.', 'userswp' ), |
| 1532 | - ) | |
| 1811 | + ) | |
| 1533 | 1812 | ); |
| 1534 | 1813 | |
| 1535 | 1814 | wp_send_json_error( array( 'message' => $message ) ); |
| 1536 | 1815 | } |
| @@ -1538,8 +1817,9 @@ | ||
| 1538 | 1817 | \WP2FA\Authenticator\Login::delete_login_nonce( $user->ID ); |
| 1539 | 1818 | |
| 1540 | 1819 | $rememberme = false; |
| 1541 | 1820 | $remember = ( isset( $_REQUEST['rememberme'] ) ) ? filter_var( $_REQUEST['rememberme'], FILTER_VALIDATE_BOOLEAN ) : ''; |
| 1821 | + | |
| 1542 | 1822 | if ( ! empty( $remember ) ) { |
| 1543 | 1823 | $rememberme = true; |
| 1544 | 1824 | } |
| 1545 | 1825 | |
| @@ -1546,13 +1826,17 @@ | ||
| 1546 | 1826 | wp_set_auth_cookie( $user->ID, $rememberme ); |
| 1547 | 1827 | |
| 1548 | 1828 | do_action( 'two_factor_user_authenticated', $user ); |
| 1549 | 1829 | |
| 1830 | + if ( defined( 'WP_2FA_PREFIX' ) ) { | |
| 1831 | + do_action( WP_2FA_PREFIX . 'user_authenticated', $user ); | |
| 1832 | + } | |
| 1833 | + | |
| 1550 | 1834 | $message = aui()->alert( |
| 1551 | - array( | |
| 1835 | + array( | |
| 1552 | 1836 | 'type' => 'success', |
| 1553 | 1837 | 'content' => __( 'Validation successful. Redirecting...', 'userswp' ), |
| 1554 | - ) | |
| 1838 | + ) | |
| 1555 | 1839 | ); |
| 1556 | 1840 | |
| 1557 | 1841 | wp_send_json_success( array( 'message' => $message ) ); |
| 1558 | 1842 | } |
| @@ -1649,9 +1933,12 @@ | ||
| 1649 | 1933 | } |
| 1650 | 1934 | |
| 1651 | 1935 | do_action( 'uwp_after_validate', $result, 'forgot', $data ); |
| 1652 | 1936 | |
| 1653 | - $user_data = get_user_by( 'email', $data['email'] ); | |
| 1937 | + $login_or_email = trim( $data['email'] ); | |
| 1938 | + $user_data = is_email( $login_or_email ) | |
| 1939 | + ? get_user_by( 'email', $login_or_email ) | |
| 1940 | + : get_user_by( 'login', $login_or_email ); | |
| 1654 | 1941 | |
| 1655 | 1942 | // if no user we fake it and bail |
| 1656 | 1943 | if ( ! $user_data ) { |
| 1657 | 1944 | $args = apply_filters( |
| @@ -1657,9 +1944,9 @@ | ||
| 1657 | 1944 | $args = apply_filters( |
| 1658 | 1945 | 'uwp_forgot_error_message', |
| 1659 | 1946 | array( |
| 1660 | 1947 | 'type' => 'error', |
| 1661 | - 'content' => __( 'Invalid email or user doesn\'t exists.', 'userswp' ), | |
| 1948 | + 'content' => __( 'Invalid username/email or user doesn\'t exist.', 'userswp' ), | |
| 1662 | 1949 | ) |
| 1663 | 1950 | ); |
| 1664 | 1951 | |
| 1665 | 1952 | $message = aui()->alert( $args ); |
| @@ -1709,9 +1996,8 @@ | ||
| 1709 | 1996 | } |
| 1710 | 1997 | |
| 1711 | 1998 | $as_password = apply_filters( 'uwp_forgot_message_as_password', false ); |
| 1712 | 1999 | |
| 1713 | - global $wpdb, $wp_hasher; | |
| 1714 | 2000 | $reset_link = ''; |
| 1715 | 2001 | |
| 1716 | 2002 | if ( $as_password ) { |
| 1717 | 2003 | $new_pass = wp_generate_password( 12, false ); |
| @@ -1723,17 +2009,21 @@ | ||
| 1723 | 2009 | $message .= '<p>' . sprintf( __( 'Username: %s', 'userswp' ), $user_data->user_login ) . '</p>'; |
| 1724 | 2010 | $message .= '<p>' . sprintf( __( 'Password: %s', 'userswp' ), $new_pass ) . '</p>'; |
| 1725 | 2011 | |
| 1726 | 2012 | } else { |
| 1727 | - $key = wp_generate_password( 20, false ); | |
| 1728 | - do_action( 'retrieve_password_key', $user_data->user_login, $key ); | |
| 2013 | + // Use WordPress core to generate, hash (wp_fast_hash in WP 6.8+), and store the reset key. | |
| 2014 | + // This ensures compatibility with check_password_reset_key() on all WP versions. | |
| 2015 | + $key = get_password_reset_key( $user_data ); | |
| 1729 | 2016 | |
| 1730 | - if ( empty( $wp_hasher ) ) { | |
| 1731 | - require_once ABSPATH . 'wp-includes/class-phpass.php'; | |
| 1732 | - $wp_hasher = new PasswordHash( 8, true ); | |
| 2017 | + if ( is_wp_error( $key ) ) { | |
| 2018 | + if ( wp_doing_ajax() ) { | |
| 2019 | + wp_send_json_error( $key->get_error_message() ); | |
| 2020 | + } else { | |
| 2021 | + $uwp_notices[] = array( 'forgot' => aui()->alert( array( 'type' => 'error', 'content' => $key->get_error_message() ) ) ); | |
| 2022 | + return; | |
| 2023 | + } | |
| 1733 | 2024 | } |
| 1734 | - $hashed = $wp_hasher->HashPassword( $key ); | |
| 1735 | - $wpdb->update( $wpdb->users, array( 'user_activation_key' => time() . ':' . $hashed ), array( 'user_login' => $user_data->user_login ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 2025 | + | |
| 1736 | 2026 | $message = '<p>' . __( 'You have requested to reset your password for the following account:', 'userswp' ) . '</p>'; |
| 1737 | 2027 | $message .= home_url( '/' ) . '</p>'; |
| 1738 | 2028 | $message .= '<p>' . sprintf( __( 'Username: %s', 'userswp' ), $user_data->user_login ) . '</p>'; |
| 1739 | 2029 | $message .= '<p>' . __( 'If this was by mistake, just ignore this email and nothing will happen.', 'userswp' ) . '</p>'; |
| @@ -2026,8 +2316,21 @@ | ||
| 2026 | 2316 | unset( $uploads_result[ $upload_file_key ] ); |
| 2027 | 2317 | } |
| 2028 | 2318 | } |
| 2029 | 2319 | |
| 2320 | + global $wpdb; | |
| 2321 | + $file_field_names = $wpdb->get_col( | |
| 2322 | + $wpdb->prepare( | |
| 2323 | + "SELECT htmlvar_name FROM " . uwp_get_table_prefix() . "uwp_form_fields WHERE form_type = %s AND field_type IN ('file','image')", | |
| 2324 | + 'account' | |
| 2325 | + ) | |
| 2326 | + ); | |
| 2327 | + foreach ( $file_field_names as $file_field_name ) { | |
| 2328 | + if ( isset( $result[ $file_field_name ] ) && ! isset( $uploads_result[ $file_field_name ] ) ) { | |
| 2329 | + unset( $result[ $file_field_name ] ); | |
| 2330 | + } | |
| 2331 | + } | |
| 2332 | + | |
| 2030 | 2333 | $result = array_merge( $result, $uploads_result ); |
| 2031 | 2334 | |
| 2032 | 2335 | $args = array( |
| 2033 | 2336 | 'ID' => get_current_user_id(), |
| @@ -2315,22 +2618,35 @@ | ||
| 2315 | 2618 | $value = uwp_get_usermeta( $user_id, $htmlvar ); |
| 2316 | 2619 | |
| 2317 | 2620 | uwp_update_usermeta( $user_id, $htmlvar, '' ); |
| 2318 | 2621 | |
| 2319 | - if ( $value ) { | |
| 2622 | + if ( $value && validate_file( $value ) === 0 ) { | |
| 2320 | 2623 | $uploads = wp_upload_dir(); |
| 2321 | 2624 | $upload_path = $uploads['basedir']; |
| 2322 | - $unlink_file = untrailingslashit( $upload_path ) . '/' . ltrim( $value, '/' ); | |
| 2323 | 2625 | |
| 2324 | - if ( is_file( $unlink_file ) && file_exists( $unlink_file ) ) { | |
| 2325 | - @unlink( $unlink_file ); | |
| 2626 | + if ( strpos( $value, 'http://' ) === 0 || strpos( $value, 'https://' ) === 0 ) { | |
| 2627 | + // Get the relative url. | |
| 2628 | + $value = uwp_get_file_relative_url( $value ); | |
| 2629 | + } | |
| 2326 | 2630 | |
| 2631 | + $unlink_file = untrailingslashit( $upload_path ) . '/' . trim( $value, '/\\' ); | |
| 2632 | + | |
| 2633 | + // Canonicalize and enforce containment inside the uploads directory before deleting. | |
| 2634 | + $real_upload_path = realpath( $upload_path ); | |
| 2635 | + $real_unlink_file = realpath( $unlink_file ); | |
| 2636 | + | |
| 2637 | + if ( $real_upload_path && $real_unlink_file && is_file( $real_unlink_file ) | |
| 2638 | + && strpos( $real_unlink_file, $real_upload_path . DIRECTORY_SEPARATOR ) === 0 ) { | |
| 2639 | + wp_delete_file( $real_unlink_file ); | |
| 2640 | + | |
| 2327 | 2641 | // For avatar/banner, also remove the original (non-thumb) file. |
| 2328 | 2642 | if ( $type ) { |
| 2329 | - $unlink_ori_file = str_replace( '_uwp_' . $type . '_thumb' . '.', '.', $unlink_file ); | |
| 2643 | + $unlink_ori_file = str_replace( '_uwp_' . $type . '_thumb' . '.', '.', $real_unlink_file ); | |
| 2644 | + $real_unlink_ori_file = realpath( $unlink_ori_file ); | |
| 2330 | 2645 | |
| 2331 | - if ( is_file( $unlink_ori_file ) && file_exists( $unlink_ori_file ) ) { | |
| 2332 | - @unlink( $unlink_ori_file ); | |
| 2646 | + if ( $real_unlink_ori_file && is_file( $real_unlink_ori_file ) | |
| 2647 | + && strpos( $real_unlink_ori_file, $real_upload_path . DIRECTORY_SEPARATOR ) === 0 ) { | |
| 2648 | + wp_delete_file( $real_unlink_ori_file ); | |
| 2333 | 2649 | } |
| 2334 | 2650 | } |
| 2335 | 2651 | } |
| 2336 | 2652 | } |
| @@ -3813,17 +4129,26 @@ | ||
| 3813 | 4129 | $site_title = uwp_get_form_label( $field ); |
| 3814 | 4130 | $required_msg = ( ! empty( $field->is_required ) && $field->required_msg != '') ? __( stripslashes( $field->required_msg ), 'userswp' ) : ''; |
| 3815 | 4131 | $validation_text = ! empty( $field->validation_msg ) ? __( stripslashes( $field->validation_msg ), 'userswp' ) : ''; |
| 3816 | 4132 | |
| 4133 | + $is_forgot_email = ( $form_type === 'forgot' && $field->htmlvar_name === 'email' ); | |
| 4134 | + $input_type = $is_forgot_email ? 'text' : 'email'; | |
| 4135 | + if ( $is_forgot_email ) { | |
| 4136 | + $site_title = __( 'Username or Email', 'userswp' ); | |
| 4137 | + $placeholder = $site_title . ( ! empty( $field->is_required ) ? ' *' : '' ); | |
| 4138 | + } else { | |
| 4139 | + $placeholder = uwp_get_field_placeholder( $field ); | |
| 4140 | + } | |
| 4141 | + | |
| 3817 | 4142 | if ( $design_style ) { |
| 3818 | 4143 | $required = ! empty( $field->is_required ) ? ' <span class="text-danger">*</span>' : ''; |
| 3819 | 4144 | |
| 3820 | 4145 | echo aui()->input( |
| 3821 | 4146 | array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 3822 | - 'type' => 'email', | |
| 4147 | + 'type' => $input_type, | |
| 3823 | 4148 | 'id' => esc_attr( $field->htmlvar_name ), |
| 3824 | 4149 | 'name' => esc_attr( $field->htmlvar_name ), |
| 3825 | - 'placeholder' => esc_attr( uwp_get_field_placeholder( $field ) ), | |
| 4150 | + 'placeholder' => esc_attr( $placeholder ), | |
| 3826 | 4151 | 'title' => esc_html( $site_title ), |
| 3827 | 4152 | 'value' => esc_attr( wp_unslash( $value ) ), |
| 3828 | 4153 | 'required' => (bool) $field->is_required, |
| 3829 | 4154 | 'validation_text' => $validation_text != '' ? esc_attr( $validation_text ) : esc_attr( $required_msg ), |
| @@ -3859,9 +4184,9 @@ | ||
| 3859 | 4184 | |
| 3860 | 4185 | <input name="<?php echo esc_attr( $field->htmlvar_name ); ?>" |
| 3861 | 4186 | class="<?php echo esc_attr( $field->css_class ); ?> uwp_textfield <?php echo esc_attr( $bs_form_control ); ?>" |
| 3862 | 4187 | id="<?php echo esc_attr( $field->htmlvar_name ); ?>" |
| 3863 | - placeholder="<?php echo esc_attr( uwp_get_field_placeholder( $field ) ); ?>" | |
| 4188 | + placeholder="<?php echo esc_attr( $placeholder ); ?>" | |
| 3864 | 4189 | value="<?php echo esc_attr( stripslashes( $value ) ); ?>" |
| 3865 | 4190 | title="<?php echo esc_attr( $site_title ); ?>" |
| 3866 | 4191 | <?php |
| 3867 | 4192 | if ( $field->is_required == 1 ) { |
| @@ -3867,9 +4192,9 @@ | ||
| 3867 | 4192 | if ( $field->is_required == 1 ) { |
| 3868 | 4193 | echo 'required="required"'; |
| 3869 | 4194 | } |
| 3870 | 4195 | ?> |
| 3871 | - type="email" | |
| 4196 | + type="<?php echo esc_attr( $input_type ); ?>" | |
| 3872 | 4197 | /> |
| 3873 | 4198 | <span class="uwp_message_note"><?php echo wp_kses_post( uwp_get_field_description( $field ) ); ?></span> |
| 3874 | 4199 | <?php if ( $field->is_required ) { ?> |
| 3875 | 4200 | <span class="uwp_message_error invalid-feedback"><?php echo esc_html__( stripslashes( $field->required_msg ), 'userswp' ); ?></span> |