PluginProbe
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP / 1.2.76
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP v1.2.76
1.2.76 1.2.75 1.2.74 1.2.73 1.2.72 1.2.71 1.2.70 1.2.69 1.2.68 1.2.67 1.2.66 1.2.65 1.2.64 1.2.63 trunk 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 All 176 releases
← All changes | includes/class-forms.php +433 -61 1.2.66 → 1.2.76 View file →
@@ -219,8 +219,16 @@
219 219 if ( strpos( $_image_url, $content_url ) !== 0 ) {
220 220 return new WP_Error( 'invalid_image', __( 'Invalid image url.', 'userswp' ) );
221 221 }
222 222
223 + // Only allow cropping the image the current user just uploaded (normalized like $image_url).
224 + $pending_key = '_uwp_pending_' . $type . '_upload';
225 + $pending_url = get_user_meta( get_current_user_id(), $pending_key, true );
226 + $pending_url = $pending_url ? str_replace( array( 'https://', 'http://' ), '', $this->normalize_url( esc_url( $pending_url ) ) ) : '';
227 + if ( empty( $pending_url ) || $pending_url !== $_image_url ) {
228 + return new WP_Error( 'crop_session_expired', __( 'Your image upload could not be verified. Please upload the image again.', 'userswp' ) );
229 + }
230 +
223 231 $filetype = wp_check_filetype( $image_url );
224 232
225 233 if ( empty( $filetype['ext'] ) ) {
226 234 return new WP_Error( 'invalid_image', __( 'Invalid image type.', 'userswp' ) );
@@ -281,8 +289,15 @@
281 289 wp_die( esc_html__( 'Something went wrong. Please contact site admin.', 'userswp' ), 403 );
282 290 }
283 291
284 292 $cropped = uwp_resizeThumbnailImage( $thumb_image_location, $image_path, $x, $y, $w, $h, $scale );
293 +
294 + // Resize returns a path even on failure; bail before touching meta or files so the crop can be retried.
295 + clearstatcache( true, $thumb_image_location );
296 + if ( ! is_file( $thumb_image_location ) ) {
297 + return new WP_Error( 'crop_failed', __( 'Could not crop the image. Please try again.', 'userswp' ) );
298 + }
299 +
285 300 $cropped = str_replace( $upload_path, $upload_url, $cropped );
286 301
287 302 // Remove previous avatar/banner
288 303 $unlink_img = '';
@@ -297,13 +312,34 @@
297 312 } else {
298 313 uwp_update_usermeta( $user_id, 'banner_thumb', $cropped );
299 314 }
300 315
301 - if ( $unlink_img && $unlink_img != $thumb_image_location && is_file( $unlink_img ) && file_exists( $unlink_img ) ) {
302 - @unlink( $unlink_img );
303 - $unlink_ori_img = str_replace( '_uwp_' . $type . '_thumb' . '.', '.', $unlink_img );
304 - if ( is_file( $unlink_ori_img ) && file_exists( $unlink_ori_img ) ) {
305 - @unlink( $unlink_ori_img );
316 + $original_key = '_uwp_' . $type . '_original';
317 + $prev_original = get_user_meta( $user_id, $original_key, true );
318 +
319 + delete_user_meta( get_current_user_id(), $pending_key );
320 + $relative_original = ltrim( wp_normalize_path( str_replace( wp_normalize_path( untrailingslashit( $upload_path ) ), '', wp_normalize_path( $image_path ) ) ), '/' );
321 + update_user_meta( $user_id, $original_key, $relative_original );
322 +
323 + // Enforce containment inside uploads before deleting, matching upload_file_remove().
324 + $real_upload_path = realpath( $upload_path );
325 + $real_unlink_img = $unlink_img ? realpath( $unlink_img ) : false;
326 +
327 + if ( $real_upload_path && $real_unlink_img && realpath( $thumb_image_location ) !== $real_unlink_img
328 + && false !== strpos( basename( $real_unlink_img ), $thumb_postfix . '.' )
329 + && 0 === strpos( $real_unlink_img, $real_upload_path . DIRECTORY_SEPARATOR )
330 + && is_file( $real_unlink_img ) ) {
331 + wp_delete_file( $real_unlink_img );
332 +
333 + // Delete the previous source only if it is the exact file this user cropped.
334 + $unlink_ori_img = str_replace( $thumb_postfix . '.', '.', $real_unlink_img );
335 + $real_unlink_ori_img = realpath( $unlink_ori_img );
336 + $expected_original = $prev_original ? realpath( untrailingslashit( $upload_path ) . '/' . $prev_original ) : false;
337 + if ( $expected_original && $real_unlink_ori_img && $expected_original === $real_unlink_ori_img
338 + && realpath( $image_path ) !== $real_unlink_ori_img
339 + && 0 === strpos( $real_unlink_ori_img, $real_upload_path . DIRECTORY_SEPARATOR )
340 + && is_file( $real_unlink_ori_img ) ) {
341 + wp_delete_file( $real_unlink_ori_img );
306 342 }
307 343 }
308 344 }
309 345
@@ -390,8 +426,13 @@
390 426 } else {
391 427 // Do nothing
392 428 }
393 429
430 + if ( in_array( $type, array( 'avatar', 'banner' ), true ) ) {
431 + delete_user_meta( $user_id, '_uwp_' . $type . '_original' );
432 + delete_user_meta( get_current_user_id(), '_uwp_pending_' . $type . '_upload' );
433 + }
434 +
394 435 if ( is_admin() ) {
395 436 if ( $user_id == get_current_user_id() ) {
396 437 $redirect_url = admin_url( 'profile.php' );
397 438 } else {
@@ -1175,8 +1216,11 @@
1175 1216 global $wp2fa;
1176 1217 if ( wp_doing_ajax() && isset( $wp2fa ) && ! empty( $wp2fa ) ) {
1177 1218 remove_action( 'wp_login', array( $wp2fa->login, 'wp_login' ), 20 );
1178 1219 }
1220 + if ( wp_doing_ajax() && class_exists( '\WP2FA\Authenticator\Login' ) ) {
1221 + remove_action( 'wp_login', array( 'WP2FA\Authenticator\Login', 'wp_login' ), 20 );
1222 + }
1179 1223
1180 1224 $user = wp_signon(
1181 1225 array(
1182 1226 'user_login' => $result['username'],
@@ -1185,10 +1229,14 @@
1185 1229 )
1186 1230 );
1187 1231
1188 1232 add_action( 'authenticate', 'gglcptch_login_check', 21, 1 );
1233 + if ( wp_doing_ajax() && class_exists( '\WP2FA\Authenticator\Login' ) ) {
1234 + add_action( 'wp_login', array( 'WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
1235 + }
1189 1236
1190 - if ( wp_doing_ajax() && ! is_wp_error( $user ) && isset( $wp2fa ) && ! empty( $wp2fa ) ) {
1237 + $wp2fa_available = ( isset( $wp2fa ) && ! empty( $wp2fa ) ) || class_exists( '\WP2FA\Authenticator\Login' );
1238 + if ( wp_doing_ajax() && ! is_wp_error( $user ) && $wp2fa_available ) {
1191 1239
1192 1240 $two_fa = $this->check_2fa( $user );
1193 1241 if ( isset( $two_fa ) && ! empty( $two_fa ) ) {
1194 1242 if ( is_wp_error( $two_fa ) ) {
@@ -1209,8 +1257,20 @@
1209 1257 }
1210 1258 }
1211 1259 }
1212 1260
1261 + if ( wp_doing_ajax() && is_wp_error( $user ) && $this->wordfence_2fa_available() ) {
1262 + $wfls_2fa = $this->check_wordfence_2fa( $user, $result );
1263 + if ( ! empty( $wfls_2fa ) ) {
1264 + wp_send_json_success(
1265 + array(
1266 + 'html' => $wfls_2fa,
1267 + 'is_2fa' => true,
1268 + )
1269 + );
1270 + }
1271 + }
1272 +
1213 1273 if ( is_wp_error( $user ) ) {
1214 1274 $message = aui()->alert(
1215 1275 array(
1216 1276 'type' => 'error',
@@ -1278,9 +1338,12 @@
1278 1338
1279 1339 return $errors;
1280 1340 }
1281 1341
1282 - $provider = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user );
1342 + $provider = $this->get_wp2fa_provider_for_user( $user );
1343 + if ( empty( $provider ) ) {
1344 + return;
1345 + }
1283 1346
1284 1347 ob_start();
1285 1348 ?>
1286 1349
@@ -1331,9 +1394,9 @@
1331 1394 echo aui()->input(
1332 1395 array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1333 1396 'type' => 'tel',
1334 1397 'id' => 'authcode',
1335 - 'name' => 'wp-2fa-email-code',
1398 + 'name' => 'authcode',
1336 1399 'placeholder' => esc_attr__( 'Verification Code', 'userswp' ),
1337 1400 'value' => '',
1338 1401 'label' => esc_html__( 'Verification Code', 'userswp' ),
1339 1402 'extra_attributes' => array(
@@ -1368,9 +1431,9 @@
1368 1431 </form>
1369 1432 </div>
1370 1433
1371 1434 <?php
1372 - $codes_remaining = \WP2FA\Authenticator\Backup_Codes::codes_remaining_for_user( $user );
1435 + $codes_remaining = $this->get_wp2fa_backup_codes_remaining( $user );
1373 1436 if ( isset( $codes_remaining ) && $codes_remaining > 0 ) {
1374 1437 ?>
1375 1438 <div class="uwp-2fa-methods-wrap" style="display:none;">
1376 1439 <form name="validate_2fa_backup_codes_form" id="validate_2fa_backup_codes_form"
@@ -1426,9 +1489,247 @@
1426 1489
1427 1490 return ob_get_clean();
1428 1491 }
1429 1492
1493 + /**
1494 + * Checks if the Wordfence Login Security module (2FA) is available.
1495 + *
1496 + * @since 1.2.5
1497 + * @package userswp
1498 + *
1499 + * @return bool
1500 + */
1501 + public function wordfence_2fa_available() {
1502 + return class_exists( '\WordfenceLS\Controller_Users' ) && class_exists( '\WordfenceLS\Controller_TOTP' );
1503 + }
1504 +
1505 + /**
1506 + * Checks whether Wordfence's 2FA requires a verification code for the
1507 + * failed login attempt and, if so, returns the markup for the code entry form.
1508 + *
1509 + * @since 1.2.5
1510 + * @package userswp
1511 + *
1512 + * @param WP_Error $error The error returned by wp_signon().
1513 + * @param array $result The validated login fields (username/password).
1514 + *
1515 + * @return string|void The 2FA form markup, or nothing if not applicable.
1516 + */
1517 + public function check_wordfence_2fa( $error, $result ) {
1518 + if ( 1 == uwp_get_option( 'disable_wordfence_2fa' ) ) {
1519 + return;
1520 + }
1521 +
1522 + if ( ! $this->wordfence_2fa_available() ) {
1523 + return;
1524 + }
1525 +
1526 + if ( ! is_wp_error( $error ) || 'wfls_twofactor_required' !== $error->get_error_code() ) {
1527 + return;
1528 + }
1529 +
1530 + $username = ! empty( $result['username'] ) ? $result['username'] : '';
1531 + if ( empty( $username ) ) {
1532 + return;
1533 + }
1534 +
1535 + $user = is_email( $username ) ? get_user_by( 'email', $username ) : get_user_by( 'login', $username );
1536 + if ( ! $user ) {
1537 + return;
1538 + }
1539 +
1540 + if ( ! \WordfenceLS\Controller_Users::shared()->has_2fa_active( $user ) ) {
1541 + return;
1542 + }
1543 +
1544 + if ( \WordfenceLS\Controller_Users::shared()->has_remembered_2fa( $user ) ) {
1545 + return;
1546 + }
1547 +
1548 + $login_nonce = wp_create_nonce( 'uwp-wfls-2fa-' . $user->ID );
1549 +
1550 + ob_start();
1551 + ?>
1552 +
1553 + <div class="uwp-2fa-methods-wrap">
1554 + <form name="validate_2fa_form" id="validate_2fa_form" class="validate_2fa_form" action="" method="post"
1555 + autocomplete="off">
1556 + <input type="hidden" name="provider" id="provider" value="wordfence"/>
1557 + <input type="hidden" name="uwp-auth-id" id="uwp-auth-id" value="<?php echo esc_attr( $user->ID ); ?>"/>
1558 + <input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce"
1559 + value="<?php echo esc_attr( $login_nonce ); ?>"/>
1560 +
1561 + <p><?php esc_html_e( 'Please enter the authentication code from your two-factor authentication app, or a recovery code, to login:', 'userswp' ); ?></p>
1562 +
1563 + <?php
1564 + echo aui()->input(
1565 + array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1566 + 'type' => 'text',
1567 + 'id' => 'authcode',
1568 + 'name' => 'authcode',
1569 + 'placeholder' => esc_attr__( 'Authentication Code', 'userswp' ),
1570 + 'value' => '',
1571 + 'label' => esc_html__( 'Authentication Code', 'userswp' ),
1572 + 'extra_attributes' => array(
1573 + 'autocomplete' => 'one-time-code',
1574 + ),
1575 + )
1576 + );
1577 +
1578 + echo aui()->button(
1579 + array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1580 + 'type' => 'submit',
1581 + 'class' => 'btn btn-primary btn-block text-uppercase uwp-2fa-submit',
1582 + 'name' => 'submit',
1583 + 'icon' => '',
1584 + 'content' => esc_html__( 'Log In', 'userswp' ),
1585 + )
1586 + );
1587 + ?>
1588 + </form>
1589 + </div>
1590 +
1591 + <?php
1592 + return ob_get_clean();
1593 + }
1594 +
1595 + public function get_wp2fa_provider_for_user( $user ) {
1596 + if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'get_available_providers_for_user' ) ) {
1597 + $provider = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user );
1598 + if ( is_array( $provider ) ) {
1599 + $provider = key( $provider );
1600 + }
1601 +
1602 + return $provider;
1603 + }
1604 +
1605 + if ( class_exists( '\WP2FA\Admin\Helpers\User_Helper' ) && method_exists( '\WP2FA\Admin\Helpers\User_Helper', 'get_enabled_method_for_user' ) ) {
1606 + return \WP2FA\Admin\Helpers\User_Helper::get_enabled_method_for_user( $user );
1607 + }
1608 +
1609 + return '';
1610 + }
1611 +
1612 + public function get_wp2fa_backup_codes_remaining( $user ) {
1613 + if ( class_exists( '\WP2FA\Methods\Backup_Codes' ) && method_exists( '\WP2FA\Methods\Backup_Codes', 'codes_remaining_for_user' ) ) {
1614 + return \WP2FA\Methods\Backup_Codes::codes_remaining_for_user( $user );
1615 + }
1616 +
1617 + if ( class_exists( '\WP2FA\Authenticator\Backup_Codes' ) && method_exists( '\WP2FA\Authenticator\Backup_Codes', 'codes_remaining_for_user' ) ) {
1618 + return \WP2FA\Authenticator\Backup_Codes::codes_remaining_for_user( $user );
1619 + }
1620 +
1621 + return 0;
1622 + }
1623 +
1624 + public function validate_wp2fa_totp_authentication( $user ) {
1625 + if ( class_exists( '\WP2FA\Methods\TOTP' ) && method_exists( '\WP2FA\Methods\TOTP', 'validate_totp_authentication' ) ) {
1626 + return \WP2FA\Methods\TOTP::validate_totp_authentication( $user );
1627 + }
1628 +
1629 + if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'validate_totp_authentication' ) ) {
1630 + return \WP2FA\Authenticator\Login::validate_totp_authentication( $user );
1631 + }
1632 +
1633 + return false;
1634 + }
1635 +
1636 + public function validate_wp2fa_email_authentication( $user ) {
1637 + if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'validate_email_authentication' ) ) {
1638 + return \WP2FA\Authenticator\Login::validate_email_authentication( $user );
1639 + }
1640 +
1641 + if ( class_exists( '\WP2FA\Authenticator\Authentication' ) && method_exists( '\WP2FA\Authenticator\Authentication', 'validate_token' ) && isset( $_REQUEST['authcode'] ) ) {
1642 + return \WP2FA\Authenticator\Authentication::validate_token( $user, sanitize_text_field( wp_unslash( $_REQUEST['authcode'] ) ) );
1643 + }
1644 +
1645 + return false;
1646 + }
1647 +
1648 + public function validate_wp2fa_backup_codes( $user ) {
1649 + if ( class_exists( '\WP2FA\Methods\Backup_Codes' ) && method_exists( '\WP2FA\Methods\Backup_Codes', 'validate_backup_codes' ) ) {
1650 + return \WP2FA\Methods\Backup_Codes::validate_backup_codes( $user );
1651 + }
1652 +
1653 + if ( class_exists( '\WP2FA\Authenticator\Backup_Codes' ) && method_exists( '\WP2FA\Authenticator\Backup_Codes', 'validate_backup_codes' ) ) {
1654 + return \WP2FA\Authenticator\Backup_Codes::validate_backup_codes( $user );
1655 + }
1656 +
1657 + return false;
1658 + }
1659 +
1660 + /**
1661 + * Validates the Wordfence 2FA code submitted from the uwp-2fa form and,
1662 + * if valid, completes the login by setting the auth cookie.
1663 + *
1664 + * @since 1.2.5
1665 + * @package userswp
1666 + *
1667 + * @param WP_User $user The user attempting to complete 2FA login.
1668 + *
1669 + * @return void
1670 + */
1671 + public function process_login_wordfence_2fa( $user ) {
1672 + if ( ! $this->wordfence_2fa_available() ) {
1673 + $message = aui()->alert(
1674 + array(
1675 + 'type' => 'error',
1676 + 'content' => __( 'Invalid request! Please try again.', 'userswp' ),
1677 + )
1678 + );
1679 +
1680 + wp_send_json_error( array( 'message' => $message ) );
1681 + }
1682 +
1683 + $nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : '';
1684 +
1685 + if ( ! wp_verify_nonce( $nonce, 'uwp-wfls-2fa-' . $user->ID ) ) {
1686 + $message = aui()->alert(
1687 + array(
1688 + 'type' => 'error',
1689 + 'content' => __( 'Invalid request! Please try again.', 'userswp' ),
1690 + )
1691 + );
1692 +
1693 + wp_send_json_error( array( 'message' => $message ) );
1694 + }
1695 +
1696 + $code = isset( $_POST['authcode'] ) ? sanitize_text_field( wp_unslash( $_POST['authcode'] ) ) : '';
1697 +
1698 + if ( empty( $code ) || true !== \WordfenceLS\Controller_TOTP::shared()->validate_2fa( $user, $code ) ) {
1699 + do_action( 'wp_login_failed', $user->user_login );
1700 +
1701 + $message = aui()->alert(
1702 + array(
1703 + 'type' => 'error',
1704 + 'content' => __( 'Invalid verification code.', 'userswp' ),
1705 + )
1706 + );
1707 +
1708 + wp_send_json_error( array( 'message' => $message ) );
1709 + }
1710 +
1711 + $remember = ( isset( $_REQUEST['rememberme'] ) ) ? filter_var( $_REQUEST['rememberme'], FILTER_VALIDATE_BOOLEAN ) : false;
1712 +
1713 + // Complete the login the same way wp_signon() would have, now that 2FA has been verified.
1714 + wp_set_auth_cookie( $user->ID, $remember );
1715 + wp_set_current_user( $user->ID );
1716 +
1717 + do_action( 'wp_login', $user->user_login, $user );
1718 +
1719 + $message = aui()->alert(
1720 + array(
1721 + 'type' => 'success',
1722 + 'content' => __( 'Validation successful. Redirecting...', 'userswp' ),
1723 + )
1724 + );
1725 +
1726 + wp_send_json_success( array( 'message' => $message ) );
1727 + }
1728 +
1430 1729 public function process_login_2fa() {
1730 + global $wp2fa;
1731 +
1431 1732 if ( ! isset( $_POST['uwp-auth-id'], $_POST['wp-auth-nonce'] ) ) {
1432 1733 return;
1433 1734 }
1434 1735
@@ -1433,46 +1734,70 @@
1433 1734 }
1434 1735
1435 1736 $auth_id = (int) $_POST['uwp-auth-id'];
1436 1737 $user = get_userdata( $auth_id );
1738 +
1437 1739 if ( ! $user ) {
1438 1740 $message = aui()->alert(
1439 - array(
1741 + array(
1440 1742 'type' => 'error',
1441 1743 'content' => __( 'Invalid user data. Please try again.', 'userswp' ),
1442 - )
1744 + )
1443 1745 );
1444 1746
1445 1747 wp_send_json_error( array( 'message' => $message ) );
1446 1748 }
1447 1749
1448 - global $wp2fa;
1750 + if ( isset( $_POST['provider'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1751 + $provider = sanitize_textarea_field( wp_unslash( $_POST['provider'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1752 + } else {
1753 + $provider = '';
1754 + }
1449 1755
1756 + if ( 'wordfence' === $provider ) {
1757 + $this->process_login_wordfence_2fa( $user );
1758 +
1759 + return;
1760 + }
1761 +
1450 1762 $nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : '';
1763 +
1451 1764 if ( true !== \WP2FA\Authenticator\Login::verify_login_nonce( $user->ID, $nonce ) ) {
1452 -
1453 1765 $message = aui()->alert(
1454 - array(
1766 + array(
1455 1767 'type' => 'error',
1456 1768 'content' => __( 'Invalid request! Please try again.', 'userswp' ),
1457 - )
1769 + )
1458 1770 );
1459 1771
1460 1772 wp_send_json_error( array( 'message' => $message ) );
1461 1773 }
1462 1774
1463 - if ( isset( $_POST['provider'] ) ) {
1464 - $provider = sanitize_textarea_field( wp_unslash( $_POST['provider'] ) );
1465 - $providers = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user );
1466 - if ( isset( $providers[ $provider ] ) ) {
1467 - $provider = $providers[ $provider ];
1468 - } elseif ( isset( $provider ) ) {
1469 - $provider = $provider;
1470 - } else {
1471 - $provider = $provider;
1775 + $error = '';
1776 +
1777 + try {
1778 + $is_enabled = \WP2FA\Admin\Controllers\Settings::is_provider_enabled_for_role( \WP2FA\Admin\Helpers\User_Helper::get_user_role( $user ), $provider );
1779 +
1780 + if ( ! $is_enabled ) {
1781 + $error = __( 'Invalid 2FA provider for user.', 'userswp' );
1472 1782 }
1783 + } catch ( \Exception $e ) {
1784 + $error = $e->getMessage();
1473 1785 }
1474 1786
1787 + if ( $error ) {
1788 + do_action( 'wp_login_failed', $user->user_login );
1789 +
1790 + $message = aui()->alert(
1791 + array(
1792 + 'type' => 'error',
1793 + 'content' => $error
1794 + )
1795 + );
1796 +
1797 + wp_send_json_error( array( 'message' => $message ) );
1798 + }
1799 +
1475 1800 // If this is an email login, or if the user failed validation previously, lets send the code to the user.
1476 1801 if ( 'email' === $provider && true !== \WP2FA\Authenticator\Login::pre_process_email_authentication( $user ) ) {
1477 1802
1478 1803 }
@@ -1477,17 +1802,16 @@
1477 1802
1478 1803 }
1479 1804
1480 1805 // Validate TOTP.
1481 - if ( 'totp' === $provider && true !== \WP2FA\Authenticator\Login::validate_totp_authentication( $user ) ) {
1482 -
1806 + if ( 'totp' === $provider && true !== $this->validate_wp2fa_totp_authentication( $user ) ) {
1483 1807 do_action( 'wp_login_failed', $user->user_login );
1484 1808
1485 1809 $message = aui()->alert(
1486 - array(
1810 + array(
1487 1811 'type' => 'error',
1488 1812 'content' => __( 'Invalid verification code.', 'userswp' ),
1489 - )
1813 + )
1490 1814 );
1491 1815
1492 1816 wp_send_json_error( array( 'message' => $message ) );
1493 1817 }
@@ -1492,27 +1816,26 @@
1492 1816 wp_send_json_error( array( 'message' => $message ) );
1493 1817 }
1494 1818
1495 1819 // Validate Email.
1496 - if ( 'email' === $provider && true !== \WP2FA\Authenticator\Login::validate_email_authentication( $user ) ) {
1497 -
1820 + if ( 'email' === $provider && true !== $this->validate_wp2fa_email_authentication( $user ) ) {
1498 1821 do_action( 'wp_login_failed', $user->user_login );
1499 1822
1500 1823 if ( isset( $_REQUEST['wp-2fa-email-code-resend'] ) && 1 == $_REQUEST['wp-2fa-email-code-resend'] ) {
1501 1824 $message = aui()->alert(
1502 - array(
1825 + array(
1503 1826 'type' => 'info',
1504 1827 'content' => __( 'A new code has been sent.', 'userswp' ),
1505 - )
1828 + )
1506 1829 );
1507 1830
1508 1831 wp_send_json_error( array( 'message' => $message ) );
1509 1832 } else {
1510 1833 $message = aui()->alert(
1511 - array(
1834 + array(
1512 1835 'type' => 'error',
1513 1836 'content' => __( 'Invalid verification code.', 'userswp' ),
1514 - )
1837 + )
1515 1838 );
1516 1839
1517 1840 wp_send_json_error( array( 'message' => $message ) );
1518 1841 }
@@ -1518,17 +1841,16 @@
1518 1841 }
1519 1842 }
1520 1843
1521 1844 // Backup Codes.
1522 - if ( 'backup_codes' === $provider && true !== \WP2FA\Authenticator\Login::validate_backup_codes( $user ) ) {
1523 -
1845 + if ( 'backup_codes' === $provider && true !== $this->validate_wp2fa_backup_codes( $user ) ) {
1524 1846 do_action( 'wp_login_failed', $user->user_login );
1525 1847
1526 1848 $message = aui()->alert(
1527 - array(
1849 + array(
1528 1850 'type' => 'error',
1529 1851 'content' => __( 'Invalid backup code.', 'userswp' ),
1530 - )
1852 + )
1531 1853 );
1532 1854
1533 1855 wp_send_json_error( array( 'message' => $message ) );
1534 1856 }
@@ -1536,8 +1858,9 @@
1536 1858 \WP2FA\Authenticator\Login::delete_login_nonce( $user->ID );
1537 1859
1538 1860 $rememberme = false;
1539 1861 $remember = ( isset( $_REQUEST['rememberme'] ) ) ? filter_var( $_REQUEST['rememberme'], FILTER_VALIDATE_BOOLEAN ) : '';
1862 +
1540 1863 if ( ! empty( $remember ) ) {
1541 1864 $rememberme = true;
1542 1865 }
1543 1866
@@ -1544,13 +1867,17 @@
1544 1867 wp_set_auth_cookie( $user->ID, $rememberme );
1545 1868
1546 1869 do_action( 'two_factor_user_authenticated', $user );
1547 1870
1871 + if ( defined( 'WP_2FA_PREFIX' ) ) {
1872 + do_action( WP_2FA_PREFIX . 'user_authenticated', $user );
1873 + }
1874 +
1548 1875 $message = aui()->alert(
1549 - array(
1876 + array(
1550 1877 'type' => 'success',
1551 1878 'content' => __( 'Validation successful. Redirecting...', 'userswp' ),
1552 - )
1879 + )
1553 1880 );
1554 1881
1555 1882 wp_send_json_success( array( 'message' => $message ) );
1556 1883 }
@@ -1647,9 +1974,12 @@
1647 1974 }
1648 1975
1649 1976 do_action( 'uwp_after_validate', $result, 'forgot', $data );
1650 1977
1651 - $user_data = get_user_by( 'email', $data['email'] );
1978 + $login_or_email = trim( $data['email'] );
1979 + $user_data = is_email( $login_or_email )
1980 + ? get_user_by( 'email', $login_or_email )
1981 + : get_user_by( 'login', $login_or_email );
1652 1982
1653 1983 // if no user we fake it and bail
1654 1984 if ( ! $user_data ) {
1655 1985 $args = apply_filters(
@@ -1655,9 +1985,9 @@
1655 1985 $args = apply_filters(
1656 1986 'uwp_forgot_error_message',
1657 1987 array(
1658 1988 'type' => 'error',
1659 - 'content' => __( 'Invalid email or user doesn\'t exists.', 'userswp' ),
1989 + 'content' => __( 'Invalid username/email or user doesn\'t exist.', 'userswp' ),
1660 1990 )
1661 1991 );
1662 1992
1663 1993 $message = aui()->alert( $args );
@@ -1707,9 +2037,8 @@
1707 2037 }
1708 2038
1709 2039 $as_password = apply_filters( 'uwp_forgot_message_as_password', false );
1710 2040
1711 - global $wpdb, $wp_hasher;
1712 2041 $reset_link = '';
1713 2042
1714 2043 if ( $as_password ) {
1715 2044 $new_pass = wp_generate_password( 12, false );
@@ -1721,17 +2050,21 @@
1721 2050 $message .= '<p>' . sprintf( __( 'Username: %s', 'userswp' ), $user_data->user_login ) . '</p>';
1722 2051 $message .= '<p>' . sprintf( __( 'Password: %s', 'userswp' ), $new_pass ) . '</p>';
1723 2052
1724 2053 } else {
1725 - $key = wp_generate_password( 20, false );
1726 - do_action( 'retrieve_password_key', $user_data->user_login, $key );
2054 + // Use WordPress core to generate, hash (wp_fast_hash in WP 6.8+), and store the reset key.
2055 + // This ensures compatibility with check_password_reset_key() on all WP versions.
2056 + $key = get_password_reset_key( $user_data );
1727 2057
1728 - if ( empty( $wp_hasher ) ) {
1729 - require_once ABSPATH . 'wp-includes/class-phpass.php';
1730 - $wp_hasher = new PasswordHash( 8, true );
2058 + if ( is_wp_error( $key ) ) {
2059 + if ( wp_doing_ajax() ) {
2060 + wp_send_json_error( $key->get_error_message() );
2061 + } else {
2062 + $uwp_notices[] = array( 'forgot' => aui()->alert( array( 'type' => 'error', 'content' => $key->get_error_message() ) ) );
2063 + return;
2064 + }
1731 2065 }
1732 - $hashed = $wp_hasher->HashPassword( $key );
1733 - $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
2066 +
1734 2067 $message = '<p>' . __( 'You have requested to reset your password for the following account:', 'userswp' ) . '</p>';
1735 2068 $message .= home_url( '/' ) . '</p>';
1736 2069 $message .= '<p>' . sprintf( __( 'Username: %s', 'userswp' ), $user_data->user_login ) . '</p>';
1737 2070 $message .= '<p>' . __( 'If this was by mistake, just ignore this email and nothing will happen.', 'userswp' ) . '</p>';
@@ -1985,9 +2318,9 @@
1985 2318 $file_obj = new UsersWP_Files();
1986 2319
1987 2320 do_action( 'uwp_before_validate', 'account' );
1988 2321
1989 - $result = uwp_validate_fields( $data, 'account', false, "AND `field_type` != 'file'" );
2322 + $result = uwp_validate_fields( $data, 'account' );
1990 2323
1991 2324 $result = apply_filters( 'uwp_validate_result', $result, 'account', $data );
1992 2325
1993 2326 if ( is_wp_error( $result ) ) {
@@ -2024,8 +2357,21 @@
2024 2357 unset( $uploads_result[ $upload_file_key ] );
2025 2358 }
2026 2359 }
2027 2360
2361 + global $wpdb;
2362 + $file_field_names = $wpdb->get_col(
2363 + $wpdb->prepare(
2364 + "SELECT htmlvar_name FROM " . uwp_get_table_prefix() . "uwp_form_fields WHERE form_type = %s AND field_type IN ('file','image')",
2365 + 'account'
2366 + )
2367 + );
2368 + foreach ( $file_field_names as $file_field_name ) {
2369 + if ( isset( $result[ $file_field_name ] ) && ! isset( $uploads_result[ $file_field_name ] ) ) {
2370 + unset( $result[ $file_field_name ] );
2371 + }
2372 + }
2373 +
2028 2374 $result = array_merge( $result, $uploads_result );
2029 2375
2030 2376 $args = array(
2031 2377 'ID' => get_current_user_id(),
@@ -2324,22 +2670,39 @@
2324 2670 }
2325 2671
2326 2672 $unlink_file = untrailingslashit( $upload_path ) . '/' . trim( $value, '/\\' );
2327 2673
2328 - if ( is_file( $unlink_file ) && file_exists( $unlink_file ) ) {
2329 - wp_delete_file( $unlink_file );
2674 + // Canonicalize and enforce containment inside the uploads directory before deleting.
2675 + $real_upload_path = realpath( $upload_path );
2676 + $real_unlink_file = realpath( $unlink_file );
2330 2677
2331 - // For avatar/banner, also remove the original (non-thumb) file.
2678 + if ( $real_upload_path && $real_unlink_file && is_file( $real_unlink_file )
2679 + && strpos( $real_unlink_file, $real_upload_path . DIRECTORY_SEPARATOR ) === 0 ) {
2680 + wp_delete_file( $real_unlink_file );
2681 +
2682 + // For avatar/banner, also remove the original (non-thumb) file, only if it is the exact file this user cropped.
2332 2683 if ( $type ) {
2333 - $unlink_ori_file = str_replace( '_uwp_' . $type . '_thumb' . '.', '.', $unlink_file );
2684 + $unlink_ori_file = str_replace( '_uwp_' . $type . '_thumb' . '.', '.', $real_unlink_file );
2685 + $real_unlink_ori_file = realpath( $unlink_ori_file );
2686 + $prev_original = get_user_meta( $user_id, '_uwp_' . $type . '_original', true );
2687 + $expected_original = $prev_original ? realpath( untrailingslashit( $upload_path ) . '/' . $prev_original ) : false;
2334 2688
2335 - if ( is_file( $unlink_ori_file ) && file_exists( $unlink_ori_file ) ) {
2336 - wp_delete_file( $unlink_ori_file );
2689 + if ( $expected_original && $real_unlink_ori_file && $expected_original === $real_unlink_ori_file
2690 + && $real_unlink_ori_file !== $real_unlink_file
2691 + && is_file( $real_unlink_ori_file )
2692 + && strpos( $real_unlink_ori_file, $real_upload_path . DIRECTORY_SEPARATOR ) === 0 ) {
2693 + wp_delete_file( $real_unlink_ori_file );
2337 2694 }
2338 2695 }
2339 2696 }
2340 2697 }
2341 2698
2699 + // Clear crop bookkeeping meta (pending upload is stored against the uploader).
2700 + if ( $type ) {
2701 + delete_user_meta( $user_id, '_uwp_' . $type . '_original' );
2702 + delete_user_meta( get_current_user_id(), '_uwp_pending_' . $type . '_upload' );
2703 + }
2704 +
2342 2705 wp_send_json_success();
2343 2706
2344 2707 wp_die();
2345 2708 }
@@ -3817,17 +4180,26 @@
3817 4180 $site_title = uwp_get_form_label( $field );
3818 4181 $required_msg = ( ! empty( $field->is_required ) && $field->required_msg != '') ? __( stripslashes( $field->required_msg ), 'userswp' ) : '';
3819 4182 $validation_text = ! empty( $field->validation_msg ) ? __( stripslashes( $field->validation_msg ), 'userswp' ) : '';
3820 4183
4184 + $is_forgot_email = ( $form_type === 'forgot' && $field->htmlvar_name === 'email' );
4185 + $input_type = $is_forgot_email ? 'text' : 'email';
4186 + if ( $is_forgot_email ) {
4187 + $site_title = __( 'Username or Email', 'userswp' );
4188 + $placeholder = $site_title . ( ! empty( $field->is_required ) ? ' *' : '' );
4189 + } else {
4190 + $placeholder = uwp_get_field_placeholder( $field );
4191 + }
4192 +
3821 4193 if ( $design_style ) {
3822 4194 $required = ! empty( $field->is_required ) ? ' <span class="text-danger">*</span>' : '';
3823 4195
3824 4196 echo aui()->input(
3825 4197 array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3826 - 'type' => 'email',
4198 + 'type' => $input_type,
3827 4199 'id' => esc_attr( $field->htmlvar_name ),
3828 4200 'name' => esc_attr( $field->htmlvar_name ),
3829 - 'placeholder' => esc_attr( uwp_get_field_placeholder( $field ) ),
4201 + 'placeholder' => esc_attr( $placeholder ),
3830 4202 'title' => esc_html( $site_title ),
3831 4203 'value' => esc_attr( wp_unslash( $value ) ),
3832 4204 'required' => (bool) $field->is_required,
3833 4205 'validation_text' => $validation_text != '' ? esc_attr( $validation_text ) : esc_attr( $required_msg ),
@@ -3863,9 +4235,9 @@
3863 4235
3864 4236 <input name="<?php echo esc_attr( $field->htmlvar_name ); ?>"
3865 4237 class="<?php echo esc_attr( $field->css_class ); ?> uwp_textfield <?php echo esc_attr( $bs_form_control ); ?>"
3866 4238 id="<?php echo esc_attr( $field->htmlvar_name ); ?>"
3867 - placeholder="<?php echo esc_attr( uwp_get_field_placeholder( $field ) ); ?>"
4239 + placeholder="<?php echo esc_attr( $placeholder ); ?>"
3868 4240 value="<?php echo esc_attr( stripslashes( $value ) ); ?>"
3869 4241 title="<?php echo esc_attr( $site_title ); ?>"
3870 4242 <?php
3871 4243 if ( $field->is_required == 1 ) {
@@ -3871,9 +4243,9 @@
3871 4243 if ( $field->is_required == 1 ) {
3872 4244 echo 'required="required"';
3873 4245 }
3874 4246 ?>
3875 - type="email"
4247 + type="<?php echo esc_attr( $input_type ); ?>"
3876 4248 />
3877 4249 <span class="uwp_message_note"><?php echo wp_kses_post( uwp_get_field_description( $field ) ); ?></span>
3878 4250 <?php if ( $field->is_required ) { ?>
3879 4251 <span class="uwp_message_error invalid-feedback"><?php echo esc_html__( stripslashes( $field->required_msg ), 'userswp' ); ?></span>