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 +220 -11 1.2.701.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 );
@@ -2127,8 +2316,21 @@
2127 2316 unset( $uploads_result[ $upload_file_key ] );
2128 2317 }
2129 2318 }
2130 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 +
2131 2333 $result = array_merge( $result, $uploads_result );
2132 2334
2133 2335 $args = array(
2134 2336 'ID' => get_current_user_id(),
@@ -2427,17 +2629,24 @@
2427 2629 }
2428 2630
2429 2631 $unlink_file = untrailingslashit( $upload_path ) . '/' . trim( $value, '/\\' );
2430 2632
2431 - if ( is_file( $unlink_file ) && file_exists( $unlink_file ) ) {
2432 - 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 );
2433 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 +
2434 2641 // For avatar/banner, also remove the original (non-thumb) file.
2435 2642 if ( $type ) {
2436 - $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 );
2437 2645
2438 - if ( is_file( $unlink_ori_file ) && file_exists( $unlink_ori_file ) ) {
2439 - 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 );
2440 2649 }
2441 2650 }
2442 2651 }
2443 2652 }