PluginProbe
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP / 1.2.73
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP v1.2.73
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 1.0.20 1.0.21 1.0.22 All 173 releases
← All changes | includes/class-forms.php +238 -17 1.2.681.2.73 View file →
@@ -1216,8 +1216,20 @@
1216 1216 }
1217 1217 }
1218 1218 }
1219 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 +
1220 1232 if ( is_wp_error( $user ) ) {
1221 1233 $message = aui()->alert(
1222 1234 array(
1223 1235 'type' => 'error',
@@ -1436,8 +1448,110 @@
1436 1448
1437 1449 return ob_get_clean();
1438 1450 }
1439 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 +
1440 1554 public function get_wp2fa_provider_for_user( $user ) {
1441 1555 if ( class_exists( '\WP2FA\Authenticator\Login' ) && method_exists( '\WP2FA\Authenticator\Login', 'get_available_providers_for_user' ) ) {
1442 1556 $provider = \WP2FA\Authenticator\Login::get_available_providers_for_user( $user );
1443 1557 if ( is_array( $provider ) ) {
@@ -1501,8 +1615,77 @@
1501 1615
1502 1616 return false;
1503 1617 }
1504 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 +
1505 1688 public function process_login_2fa() {
1506 1689 global $wp2fa;
1507 1690
1508 1691 if ( ! isset( $_POST['uwp-auth-id'], $_POST['wp-auth-nonce'] ) ) {
@@ -1522,8 +1705,20 @@
1522 1705
1523 1706 wp_send_json_error( array( 'message' => $message ) );
1524 1707 }
1525 1708
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 + }
1714 +
1715 + if ( 'wordfence' === $provider ) {
1716 + $this->process_login_wordfence_2fa( $user );
1717 +
1718 + return;
1719 + }
1720 +
1526 1721 $nonce = ( isset( $_POST['wp-auth-nonce'] ) ) ? sanitize_textarea_field( wp_unslash( $_POST['wp-auth-nonce'] ) ) : '';
1527 1722
1528 1723 if ( true !== \WP2FA\Authenticator\Login::verify_login_nonce( $user->ID, $nonce ) ) {
1529 1724 $message = aui()->alert(
@@ -1535,14 +1730,8 @@
1535 1730
1536 1731 wp_send_json_error( array( 'message' => $message ) );
1537 1732 }
1538 1733
1539 - if ( isset( $_POST['provider'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1540 - $provider = sanitize_textarea_field( wp_unslash( $_POST['provider'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1541 - } else {
1542 - $provider = '';
1543 - }
1544 -
1545 1734 $error = '';
1546 1735
1547 1736 try {
1548 1737 $is_enabled = \WP2FA\Admin\Controllers\Settings::is_provider_enabled_for_role( \WP2FA\Admin\Helpers\User_Helper::get_user_role( $user ), $provider );
@@ -1744,9 +1933,12 @@
1744 1933 }
1745 1934
1746 1935 do_action( 'uwp_after_validate', $result, 'forgot', $data );
1747 1936
1748 - $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 );
1749 1941
1750 1942 // if no user we fake it and bail
1751 1943 if ( ! $user_data ) {
1752 1944 $args = apply_filters(
@@ -1752,9 +1944,9 @@
1752 1944 $args = apply_filters(
1753 1945 'uwp_forgot_error_message',
1754 1946 array(
1755 1947 'type' => 'error',
1756 - 'content' => __( 'Invalid email or user doesn\'t exists.', 'userswp' ),
1948 + 'content' => __( 'Invalid username/email or user doesn\'t exist.', 'userswp' ),
1757 1949 )
1758 1950 );
1759 1951
1760 1952 $message = aui()->alert( $args );
@@ -2124,8 +2316,21 @@
2124 2316 unset( $uploads_result[ $upload_file_key ] );
2125 2317 }
2126 2318 }
2127 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 +
2128 2333 $result = array_merge( $result, $uploads_result );
2129 2334
2130 2335 $args = array(
2131 2336 'ID' => get_current_user_id(),
@@ -2424,17 +2629,24 @@
2424 2629 }
2425 2630
2426 2631 $unlink_file = untrailingslashit( $upload_path ) . '/' . trim( $value, '/\\' );
2427 2632
2428 - if ( is_file( $unlink_file ) && file_exists( $unlink_file ) ) {
2429 - wp_delete_file( $unlink_file );
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 );
2430 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 +
2431 2641 // For avatar/banner, also remove the original (non-thumb) file.
2432 2642 if ( $type ) {
2433 - $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 );
2434 2645
2435 - if ( is_file( $unlink_ori_file ) && file_exists( $unlink_ori_file ) ) {
2436 - wp_delete_file( $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 );
2437 2649 }
2438 2650 }
2439 2651 }
2440 2652 }
@@ -3917,17 +4129,26 @@
3917 4129 $site_title = uwp_get_form_label( $field );
3918 4130 $required_msg = ( ! empty( $field->is_required ) && $field->required_msg != '') ? __( stripslashes( $field->required_msg ), 'userswp' ) : '';
3919 4131 $validation_text = ! empty( $field->validation_msg ) ? __( stripslashes( $field->validation_msg ), 'userswp' ) : '';
3920 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 +
3921 4142 if ( $design_style ) {
3922 4143 $required = ! empty( $field->is_required ) ? ' <span class="text-danger">*</span>' : '';
3923 4144
3924 4145 echo aui()->input(
3925 4146 array( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3926 - 'type' => 'email',
4147 + 'type' => $input_type,
3927 4148 'id' => esc_attr( $field->htmlvar_name ),
3928 4149 'name' => esc_attr( $field->htmlvar_name ),
3929 - 'placeholder' => esc_attr( uwp_get_field_placeholder( $field ) ),
4150 + 'placeholder' => esc_attr( $placeholder ),
3930 4151 'title' => esc_html( $site_title ),
3931 4152 'value' => esc_attr( wp_unslash( $value ) ),
3932 4153 'required' => (bool) $field->is_required,
3933 4154 'validation_text' => $validation_text != '' ? esc_attr( $validation_text ) : esc_attr( $required_msg ),
@@ -3963,9 +4184,9 @@
3963 4184
3964 4185 <input name="<?php echo esc_attr( $field->htmlvar_name ); ?>"
3965 4186 class="<?php echo esc_attr( $field->css_class ); ?> uwp_textfield <?php echo esc_attr( $bs_form_control ); ?>"
3966 4187 id="<?php echo esc_attr( $field->htmlvar_name ); ?>"
3967 - placeholder="<?php echo esc_attr( uwp_get_field_placeholder( $field ) ); ?>"
4188 + placeholder="<?php echo esc_attr( $placeholder ); ?>"
3968 4189 value="<?php echo esc_attr( stripslashes( $value ) ); ?>"
3969 4190 title="<?php echo esc_attr( $site_title ); ?>"
3970 4191 <?php
3971 4192 if ( $field->is_required == 1 ) {
@@ -3971,9 +4192,9 @@
3971 4192 if ( $field->is_required == 1 ) {
3972 4193 echo 'required="required"';
3973 4194 }
3974 4195 ?>
3975 - type="email"
4196 + type="<?php echo esc_attr( $input_type ); ?>"
3976 4197 />
3977 4198 <span class="uwp_message_note"><?php echo wp_kses_post( uwp_get_field_description( $field ) ); ?></span>
3978 4199 <?php if ( $field->is_required ) { ?>
3979 4200 <span class="uwp_message_error invalid-feedback"><?php echo esc_html__( stripslashes( $field->required_msg ), 'userswp' ); ?></span>