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 +445 -69 1.2.63 → 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
@@ -360,22 +396,20 @@
360 396 if ( ! is_user_logged_in() ) {
361 397 return false;
362 398 }
363 399
364 - if ( empty( $_POST['uwp_reset_nonce'] ) || ! wp_verify_nonce( $_POST['uwp_reset_nonce'], 'uwp_reset_nonce_' . $type ) ) {
365 - return;
366 - }
367 -
368 400 if ( is_admin() && defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) {
369 401 $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'] ) ) {
402 + } elseif ( is_admin() && current_user_can( 'manage_options' ) && ! empty( $_GET['user_id'] ) && is_numeric( $_GET['user_id'] ) ) {
372 403 $user_id = absint( $_GET['user_id'] );
373 - // Otherwise something is wrong.
374 404 } else {
375 405 $user_id = get_current_user_id();
376 406 }
377 407
408 + if ( empty( $_POST['uwp_reset_nonce'] ) || ! wp_verify_nonce( $_POST['uwp_reset_nonce'], 'uwp_reset_nonce_' . $type . '_' . $user_id ) ) {
409 + return;
410 + }
411 +
378 412 $errors = new WP_Error();
379 413 if ( empty( $user_id ) ) {
380 414 $errors->add( 'something_wrong', __( 'Something went wrong. Please try again.', 'userswp' ) );
381 415 }
@@ -392,8 +426,13 @@
392 426 } else {
393 427 // Do nothing
394 428 }
395 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 +
396 435 if ( is_admin() ) {
397 436 if ( $user_id == get_current_user_id() ) {
398 437 $redirect_url = admin_url( 'profile.php' );
399 438 } else {
@@ -1177,8 +1216,11 @@
1177 1216 global $wp2fa;
1178 1217 if ( wp_doing_ajax() && isset( $wp2fa ) && ! empty( $wp2fa ) ) {
1179 1218 remove_action( 'wp_login', array( $wp2fa->login, 'wp_login' ), 20 );
1180 1219 }
1220 + if ( wp_doing_ajax() && class_exists( '\WP2FA\Authenticator\Login' ) ) {
1221 + remove_action( 'wp_login', array( 'WP2FA\Authenticator\Login', 'wp_login' ), 20 );
1222 + }
1181 1223
1182 1224 $user = wp_signon(
1183 1225 array(
1184 1226 'user_login' => $result['username'],
@@ -1187,10 +1229,14 @@
1187 1229 )
1188 1230 );
1189 1231
1190 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 + }
1191 1236
1192 - 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 ) {
1193 1239
1194 1240 $two_fa = $this->check_2fa( $user );
1195 1241 if ( isset( $two_fa ) && ! empty( $two_fa ) ) {
1196 1242 if ( is_wp_error( $two_fa ) ) {
@@ -1211,8 +1257,20 @@
1211 1257 }
1212 1258 }
1213 1259 }
1214 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 +
1215 1273 if ( is_wp_error( $user ) ) {
1216 1274 $message = aui()->alert(
1217 1275 array(
1218 1276 'type' => 'error',
@@ -1280,9 +1338,12 @@
1280 1338
1281 1339 return $errors;
1282 1340 }
1283 1341
1284 - $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 + }
1285 1346
1286 1347 ob_start();
1287 1348 ?>
1288 1349
@@ -1333,9 +1394,9 @@
1333 1394 echo aui()->input(
1334 1395 array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1335 1396 'type' => 'tel',
1336 1397 'id' => 'authcode',
1337 - 'name' => 'wp-2fa-email-code',
1398 + 'name' => 'authcode',
1338 1399 'placeholder' => esc_attr__( 'Verification Code', 'userswp' ),
1339 1400 'value' => '',
1340 1401 'label' => esc_html__( 'Verification Code', 'userswp' ),
1341 1402 'extra_attributes' => array(
@@ -1370,9 +1431,9 @@
1370 1431 </form>
1371 1432 </div>
1372 1433
1373 1434 <?php
1374 - $codes_remaining = \WP2FA\Authenticator\Backup_Codes::codes_remaining_for_user( $user );
1435 + $codes_remaining = $this->get_wp2fa_backup_codes_remaining( $user );
1375 1436 if ( isset( $codes_remaining ) && $codes_remaining > 0 ) {
1376 1437 ?>
1377 1438 <div class="uwp-2fa-methods-wrap" style="display:none;">
1378 1439 <form name="validate_2fa_backup_codes_form" id="validate_2fa_backup_codes_form"
@@ -1428,9 +1489,247 @@
1428 1489
1429 1490 return ob_get_clean();
1430 1491 }
1431 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 +
1432 1729 public function process_login_2fa() {
1730 + global $wp2fa;
1731 +
1433 1732 if ( ! isset( $_POST['uwp-auth-id'], $_POST['wp-auth-nonce'] ) ) {
1434 1733 return;
1435 1734 }
1436 1735
@@ -1435,46 +1734,70 @@
1435 1734 }
1436 1735
1437 1736 $auth_id = (int) $_POST['uwp-auth-id'];
1438 1737 $user = get_userdata( $auth_id );
1738 +
1439 1739 if ( ! $user ) {
1440 1740 $message = aui()->alert(
1441 - array(
1741 + array(
1442 1742 'type' => 'error',
1443 1743 'content' => __( 'Invalid user data. Please try again.', 'userswp' ),
1444 - )
1744 + )
1445 1745 );
1446 1746
1447 1747 wp_send_json_error( array( 'message' => $message ) );
1448 1748 }
1449 1749
1450 - 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 + }
1451 1755
1756 + if ( 'wordfence' === $provider ) {
1757 + $this->process_login_wordfence_2fa( $user );
1758 +
1759 + return;
1760 + }
1761 +
1452 1762 $nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : '';
1763 +
1453 1764 if ( true !== \WP2FA\Authenticator\Login::verify_login_nonce( $user->ID, $nonce ) ) {
1454 -
1455 1765 $message = aui()->alert(
1456 - array(
1766 + array(
1457 1767 'type' => 'error',
1458 1768 'content' => __( 'Invalid request! Please try again.', 'userswp' ),
1459 - )
1769 + )
1460 1770 );
1461 1771
1462 1772 wp_send_json_error( array( 'message' => $message ) );
1463 1773 }
1464 1774
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;
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' );
1474 1782 }
1783 + } catch ( \Exception $e ) {
1784 + $error = $e->getMessage();
1475 1785 }
1476 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 +
1477 1800 // If this is an email login, or if the user failed validation previously, lets send the code to the user.
1478 1801 if ( 'email' === $provider && true !== \WP2FA\Authenticator\Login::pre_process_email_authentication( $user ) ) {
1479 1802
1480 1803 }
@@ -1479,17 +1802,16 @@
1479 1802
1480 1803 }
1481 1804
1482 1805 // Validate TOTP.
1483 - if ( 'totp' === $provider && true !== \WP2FA\Authenticator\Login::validate_totp_authentication( $user ) ) {
1484 -
1806 + if ( 'totp' === $provider && true !== $this->validate_wp2fa_totp_authentication( $user ) ) {
1485 1807 do_action( 'wp_login_failed', $user->user_login );
1486 1808
1487 1809 $message = aui()->alert(
1488 - array(
1810 + array(
1489 1811 'type' => 'error',
1490 1812 'content' => __( 'Invalid verification code.', 'userswp' ),
1491 - )
1813 + )
1492 1814 );
1493 1815
1494 1816 wp_send_json_error( array( 'message' => $message ) );
1495 1817 }
@@ -1494,27 +1816,26 @@
1494 1816 wp_send_json_error( array( 'message' => $message ) );
1495 1817 }
1496 1818
1497 1819 // Validate Email.
1498 - if ( 'email' === $provider && true !== \WP2FA\Authenticator\Login::validate_email_authentication( $user ) ) {
1499 -
1820 + if ( 'email' === $provider && true !== $this->validate_wp2fa_email_authentication( $user ) ) {
1500 1821 do_action( 'wp_login_failed', $user->user_login );
1501 1822
1502 1823 if ( isset( $_REQUEST['wp-2fa-email-code-resend'] ) && 1 == $_REQUEST['wp-2fa-email-code-resend'] ) {
1503 1824 $message = aui()->alert(
1504 - array(
1825 + array(
1505 1826 'type' => 'info',
1506 1827 'content' => __( 'A new code has been sent.', 'userswp' ),
1507 - )
1828 + )
1508 1829 );
1509 1830
1510 1831 wp_send_json_error( array( 'message' => $message ) );
1511 1832 } else {
1512 1833 $message = aui()->alert(
1513 - array(
1834 + array(
1514 1835 'type' => 'error',
1515 1836 'content' => __( 'Invalid verification code.', 'userswp' ),
1516 - )
1837 + )
1517 1838 );
1518 1839
1519 1840 wp_send_json_error( array( 'message' => $message ) );
1520 1841 }
@@ -1520,17 +1841,16 @@
1520 1841 }
1521 1842 }
1522 1843
1523 1844 // Backup Codes.
1524 - if ( 'backup_codes' === $provider && true !== \WP2FA\Authenticator\Login::validate_backup_codes( $user ) ) {
1525 -
1845 + if ( 'backup_codes' === $provider && true !== $this->validate_wp2fa_backup_codes( $user ) ) {
1526 1846 do_action( 'wp_login_failed', $user->user_login );
1527 1847
1528 1848 $message = aui()->alert(
1529 - array(
1849 + array(
1530 1850 'type' => 'error',
1531 1851 'content' => __( 'Invalid backup code.', 'userswp' ),
1532 - )
1852 + )
1533 1853 );
1534 1854
1535 1855 wp_send_json_error( array( 'message' => $message ) );
1536 1856 }
@@ -1538,8 +1858,9 @@
1538 1858 \WP2FA\Authenticator\Login::delete_login_nonce( $user->ID );
1539 1859
1540 1860 $rememberme = false;
1541 1861 $remember = ( isset( $_REQUEST['rememberme'] ) ) ? filter_var( $_REQUEST['rememberme'], FILTER_VALIDATE_BOOLEAN ) : '';
1862 +
1542 1863 if ( ! empty( $remember ) ) {
1543 1864 $rememberme = true;
1544 1865 }
1545 1866
@@ -1546,13 +1867,17 @@
1546 1867 wp_set_auth_cookie( $user->ID, $rememberme );
1547 1868
1548 1869 do_action( 'two_factor_user_authenticated', $user );
1549 1870
1871 + if ( defined( 'WP_2FA_PREFIX' ) ) {
1872 + do_action( WP_2FA_PREFIX . 'user_authenticated', $user );
1873 + }
1874 +
1550 1875 $message = aui()->alert(
1551 - array(
1876 + array(
1552 1877 'type' => 'success',
1553 1878 'content' => __( 'Validation successful. Redirecting...', 'userswp' ),
1554 - )
1879 + )
1555 1880 );
1556 1881
1557 1882 wp_send_json_success( array( 'message' => $message ) );
1558 1883 }
@@ -1649,9 +1974,12 @@
1649 1974 }
1650 1975
1651 1976 do_action( 'uwp_after_validate', $result, 'forgot', $data );
1652 1977
1653 - $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 );
1654 1982
1655 1983 // if no user we fake it and bail
1656 1984 if ( ! $user_data ) {
1657 1985 $args = apply_filters(
@@ -1657,9 +1985,9 @@
1657 1985 $args = apply_filters(
1658 1986 'uwp_forgot_error_message',
1659 1987 array(
1660 1988 'type' => 'error',
1661 - 'content' => __( 'Invalid email or user doesn\'t exists.', 'userswp' ),
1989 + 'content' => __( 'Invalid username/email or user doesn\'t exist.', 'userswp' ),
1662 1990 )
1663 1991 );
1664 1992
1665 1993 $message = aui()->alert( $args );
@@ -1709,9 +2037,8 @@
1709 2037 }
1710 2038
1711 2039 $as_password = apply_filters( 'uwp_forgot_message_as_password', false );
1712 2040
1713 - global $wpdb, $wp_hasher;
1714 2041 $reset_link = '';
1715 2042
1716 2043 if ( $as_password ) {
1717 2044 $new_pass = wp_generate_password( 12, false );
@@ -1723,17 +2050,21 @@
1723 2050 $message .= '<p>' . sprintf( __( 'Username: %s', 'userswp' ), $user_data->user_login ) . '</p>';
1724 2051 $message .= '<p>' . sprintf( __( 'Password: %s', 'userswp' ), $new_pass ) . '</p>';
1725 2052
1726 2053 } else {
1727 - $key = wp_generate_password( 20, false );
1728 - 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 );
1729 2057
1730 - if ( empty( $wp_hasher ) ) {
1731 - require_once ABSPATH . 'wp-includes/class-phpass.php';
1732 - $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 + }
1733 2065 }
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
2066 +
1736 2067 $message = '<p>' . __( 'You have requested to reset your password for the following account:', 'userswp' ) . '</p>';
1737 2068 $message .= home_url( '/' ) . '</p>';
1738 2069 $message .= '<p>' . sprintf( __( 'Username: %s', 'userswp' ), $user_data->user_login ) . '</p>';
1739 2070 $message .= '<p>' . __( 'If this was by mistake, just ignore this email and nothing will happen.', 'userswp' ) . '</p>';
@@ -2026,8 +2357,21 @@
2026 2357 unset( $uploads_result[ $upload_file_key ] );
2027 2358 }
2028 2359 }
2029 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 +
2030 2374 $result = array_merge( $result, $uploads_result );
2031 2375
2032 2376 $args = array(
2033 2377 'ID' => get_current_user_id(),
@@ -2315,27 +2659,50 @@
2315 2659 $value = uwp_get_usermeta( $user_id, $htmlvar );
2316 2660
2317 2661 uwp_update_usermeta( $user_id, $htmlvar, '' );
2318 2662
2319 - if ( $value ) {
2663 + if ( $value && validate_file( $value ) === 0 ) {
2320 2664 $uploads = wp_upload_dir();
2321 2665 $upload_path = $uploads['basedir'];
2322 - $unlink_file = untrailingslashit( $upload_path ) . '/' . ltrim( $value, '/' );
2323 2666
2324 - if ( is_file( $unlink_file ) && file_exists( $unlink_file ) ) {
2325 - @unlink( $unlink_file );
2667 + if ( strpos( $value, 'http://' ) === 0 || strpos( $value, 'https://' ) === 0 ) {
2668 + // Get the relative url.
2669 + $value = uwp_get_file_relative_url( $value );
2670 + }
2326 2671
2327 - // For avatar/banner, also remove the original (non-thumb) file.
2672 + $unlink_file = untrailingslashit( $upload_path ) . '/' . trim( $value, '/\\' );
2673 +
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 );
2677 +
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.
2328 2683 if ( $type ) {
2329 - $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;
2330 2688
2331 - if ( is_file( $unlink_ori_file ) && file_exists( $unlink_ori_file ) ) {
2332 - @unlink( $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 );
2333 2694 }
2334 2695 }
2335 2696 }
2336 2697 }
2337 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 +
2338 2705 wp_send_json_success();
2339 2706
2340 2707 wp_die();
2341 2708 }
@@ -3813,17 +4180,26 @@
3813 4180 $site_title = uwp_get_form_label( $field );
3814 4181 $required_msg = ( ! empty( $field->is_required ) && $field->required_msg != '') ? __( stripslashes( $field->required_msg ), 'userswp' ) : '';
3815 4182 $validation_text = ! empty( $field->validation_msg ) ? __( stripslashes( $field->validation_msg ), 'userswp' ) : '';
3816 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 +
3817 4193 if ( $design_style ) {
3818 4194 $required = ! empty( $field->is_required ) ? ' <span class="text-danger">*</span>' : '';
3819 4195
3820 4196 echo aui()->input(
3821 4197 array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3822 - 'type' => 'email',
4198 + 'type' => $input_type,
3823 4199 'id' => esc_attr( $field->htmlvar_name ),
3824 4200 'name' => esc_attr( $field->htmlvar_name ),
3825 - 'placeholder' => esc_attr( uwp_get_field_placeholder( $field ) ),
4201 + 'placeholder' => esc_attr( $placeholder ),
3826 4202 'title' => esc_html( $site_title ),
3827 4203 'value' => esc_attr( wp_unslash( $value ) ),
3828 4204 'required' => (bool) $field->is_required,
3829 4205 'validation_text' => $validation_text != '' ? esc_attr( $validation_text ) : esc_attr( $required_msg ),
@@ -3859,9 +4235,9 @@
3859 4235
3860 4236 <input name="<?php echo esc_attr( $field->htmlvar_name ); ?>"
3861 4237 class="<?php echo esc_attr( $field->css_class ); ?> uwp_textfield <?php echo esc_attr( $bs_form_control ); ?>"
3862 4238 id="<?php echo esc_attr( $field->htmlvar_name ); ?>"
3863 - placeholder="<?php echo esc_attr( uwp_get_field_placeholder( $field ) ); ?>"
4239 + placeholder="<?php echo esc_attr( $placeholder ); ?>"
3864 4240 value="<?php echo esc_attr( stripslashes( $value ) ); ?>"
3865 4241 title="<?php echo esc_attr( $site_title ); ?>"
3866 4242 <?php
3867 4243 if ( $field->is_required == 1 ) {
@@ -3867,9 +4243,9 @@
3867 4243 if ( $field->is_required == 1 ) {
3868 4244 echo 'required="required"';
3869 4245 }
3870 4246 ?>
3871 - type="email"
4247 + type="<?php echo esc_attr( $input_type ); ?>"
3872 4248 />
3873 4249 <span class="uwp_message_note"><?php echo wp_kses_post( uwp_get_field_description( $field ) ); ?></span>
3874 4250 <?php if ( $field->is_required ) { ?>
3875 4251 <span class="uwp_message_error invalid-feedback"><?php echo esc_html__( stripslashes( $field->required_msg ), 'userswp' ); ?></span>