Skip to:
Content

BuddyPress.org

Changeset 14071


Ignore:
Timestamp:
11/03/2024 06:44:17 PM (5 months ago)
Author:
espellcaste
Message:

Misc changes to the signups and pending accounts.

We are improving how signups and pending accounts are handled in BuddyPress.

  • activation emails resend are blocked for one hour, by default;
  • emails are checked if they are already in use in a signup;
  • signup endpoint (https://developer.buddypress.org/bp-rest-api/reference/signup/) returns a useful error when feature is disabled;
  • Signup::resend: Added the ability to resend to a single ID, instead of an array of IDs.

Props niftythree and imath.

Closes https://github.com/buddypress/buddypress/pull/396
See #9229 and #9145
Fixes #9137

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-core/bp-core-rest-api.php

    r14048 r14071  
    393393}
    394394add_filter( 'rest_pre_dispatch', 'bp_rest_api_v1_dispatch_error', 10, 3 );
     395
     396/**
     397 * Filter the WP REST API response to return a 403 if the signup feature is disabled.
     398 *
     399 * @since 15.0.0
     400 *
     401 * @param mixed           $result Response to replace the requested version with. Can be anything
     402 *                                a normal endpoint can return, or null to not hijack the request.
     403 * @param WP_REST_Server  $server Server instance.
     404 * @param WP_REST_Request $request Request used to generate the response.
     405 *
     406 * @return mixed
     407 */
     408function bp_rest_api_signup_disabled_feature_dispatch_error( $result, $server, $request ) {
     409
     410    // Bail early if the BP REST plugin is active.
     411    if ( bp_rest_is_plugin_active() ) {
     412        return $result;
     413    }
     414
     415    $route = $request->get_route();
     416
     417    if ( empty( $route ) || ! str_contains( $route, 'buddypress/v2/signup' ) ) {
     418        return $result;
     419    }
     420
     421    // Bail early if signups are allowed.
     422    if ( bp_get_signup_allowed() ) {
     423        return $result;
     424    }
     425
     426    return new WP_Error(
     427        'rest_no_route',
     428        __( 'BuddyPress: The user signup feature is currently disabled. Please activate this feature to proceed.', 'buddypress' ),
     429        array( 'status' => 403 )
     430    );
     431}
     432add_filter( 'rest_pre_dispatch', 'bp_rest_api_signup_disabled_feature_dispatch_error', 10, 3 );
  • trunk/src/bp-members/bp-members-functions.php

    r13984 r14071  
    15931593 * Performs the following checks:
    15941594 *   - Is the email address well-formed?
    1595  *   - Is the email address already used?
     1595 *   - Is the email address already used in a user?
     1596 *   - Is the email address already used in a signup?
    15961597 *   - If there are disallowed email domains, is the current domain among them?
    1597  *   - If there's an email domain whitelist, is the current domain on it?
     1598 *   - If there's an email domain allowlist, is the current domain on it?
    15981599 *
    15991600 * @since 1.6.2
     1601 * @since 15.0.0 Check if the email address is already used in a signup.
    16001602 *
    16011603 * @param string $user_email The email being checked.
     
    16301632    }
    16311633
    1632     // Is the email already in use?
     1634    // Is the email already in use in a user?
    16331635    if ( email_exists( $user_email ) ) {
    16341636        $errors['in_use'] = 1;
     1637    }
     1638
     1639    // Is the email already in use in a signup?
     1640    if ( ! isset( $errors['in_use'] ) ) {
     1641        $signups = BP_Signup::get(
     1642            array( 'user_email' => $user_email )
     1643        );
     1644
     1645        $signup = isset( $signups['signups'] ) && ! empty( $signups['signups'][0] );
     1646
     1647        if ( $signup ) {
     1648            $errors['in_use'] = 1;
     1649        }
    16351650    }
    16361651
     
    17491764        // Check into signups.
    17501765        $signups = BP_Signup::get(
    1751             array(
    1752                 'user_login' => $user_name,
    1753             )
     1766            array( 'user_login' => $user_name )
    17541767        );
    17551768
    1756         $signup = isset( $signups['signups'] ) && ! empty( $signups['signups'][0] ) ? $signups['signups'][0] : false;
     1769        $signup           = isset( $signups['signups'] ) && ! empty( $signups['signups'][0] );
     1770        $user_name_exists = ( empty( $signup ) && username_exists( $user_name ) ) || ! empty( $signup );
    17571771
    17581772        // Check if the username has been used already.
    1759         if ( username_exists( $user_name ) || ! empty( $signup ) ) {
     1773        if ( true === $user_name_exists ) {
    17601774            $errors->add( 'user_name', __( 'Sorry, that username already exists!', 'buddypress' ) );
    17611775        }
    17621776
    1763         // Validate the email address and process the validation results into
    1764         // error messages.
    1765         $validate_email = bp_core_validate_email_address( $user_email );
    1766         bp_core_add_validation_error_messages( $errors, $validate_email );
     1777        // Validate the email address.
     1778        bp_core_add_validation_error_messages(
     1779            $errors,
     1780            bp_core_validate_email_address( $user_email )
     1781        );
    17671782
    17681783        // Assemble the return array.
     
    17731788        );
    17741789
    1775         // Apply WPMU legacy filter.
     1790        /** This filter is documented in wp-includes/ms-functions.php */
    17761791        $result = apply_filters( 'wpmu_validate_user_signup', $result );
    17771792    }
     
    24872502 *
    24882503 * @since 2.0.0
     2504 * @since 15.0.0 Return an error when the activation email resend has been blocked temporarily.
    24892505 *
    24902506 * @global string $error The error message.
     
    25002516
    25012517    // Verify nonce.
    2502     if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'bp-resend-activation' ) ) {
    2503         die( 'Security check' );
    2504     }
    2505 
    2506     $signup_id = (int) $_GET['id'];
     2518    if ( ! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'bp-resend-activation' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     2519        wp_die( esc_html__( 'There was a problem performing this action. Please try again.', 'buddypress' ) );
     2520    }
     2521
     2522    $signups = BP_Signup::get(
     2523        array( 'include' => (int) $_GET['id'] )
     2524    );
     2525
     2526    // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
     2527
     2528    if ( empty( $signups['signups'] ) || ! is_array( $signups['signups'] ) || empty( $signups['signups'][0] ) ) {
     2529        $error = __( '<strong>Error</strong>: Invalid signup id.', 'buddypress' );
     2530        return;
     2531    }
     2532
     2533    $signup = $signups['signups'][0];
     2534
     2535    if ( false === BP_Signup::allow_activation_resend( $signup ) ) {
     2536        $error = __( '<strong>Error</strong>: You\'ve reached the limit for resending your account activation email. Please wait a few minutes and try again. If you continue to experience issues, contact support for assistance.', 'buddypress' );
     2537        return;
     2538    }
    25072539
    25082540    // Resend the activation email.
    25092541    // also updates the 'last sent' and '# of emails sent' values.
    2510     $resend = BP_Signup::resend( array( $signup_id ) );
     2542    $resend = BP_Signup::resend( $signup->id );
    25112543
    25122544    // Add feedback message.
     
    25162548        $error = __( 'Activation email resent! Please check your inbox or spam folder.', 'buddypress' );
    25172549    }
     2550
     2551    // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
    25182552}
    25192553add_action( 'login_form_bp-resend-activation', 'bp_members_login_resend_activation_email' );
  • trunk/src/bp-members/bp-members-template.php

    r14011 r14071  
    30663066
    30673067        /**
    3068          * Filters whether or not new signups are allowed.
     3068         * Filters whether new signups are allowed.
    30693069         *
    30703070         * @since 1.5.0
  • trunk/src/bp-members/classes/class-bp-members-signup-rest-controller.php

    r14026 r14071  
    765765     */
    766766    public function signup_resend_activation_email( $request ) {
    767         $signup_id = $request->get_param( 'id' );
    768         $send      = \BP_Signup::resend( array( $signup_id ) );
     767        $signup = $this->get_signup_object( $request->get_param( 'id' ) );
     768        $send   = $signup::resend( $signup->id );
     769
     770        if ( empty( $send ) ) {
     771            return new WP_Error(
     772                'bp_rest_signup_resend_activation_email_fail',
     773                __( 'There was a problem performing this action. Please try again.', 'buddypress' ),
     774                array( 'status' => 500 )
     775            );
     776        }
    769777
    770778        if ( ! empty( $send['errors'] ) ) {
     
    772780                'bp_rest_signup_resend_activation_email_fail',
    773781                __( 'Your account has already been activated.', 'buddypress' ),
    774                 array(
    775                     'status' => 500,
    776                 )
     782                array( 'status' => 500 )
    777783            );
    778784        }
     
    809815                'bp_rest_invalid_id',
    810816                __( 'Invalid signup id.', 'buddypress' ),
    811                 array(
    812                     'status' => 404,
    813                 )
     817                array( 'status' => 404 )
     818            );
     819        }
     820
     821        if ( true === $retval && false === BP_Signup::allow_activation_resend( $signup ) ) {
     822            $retval = new WP_Error(
     823                'bp_rest_signup_resend_activation_email_fail',
     824                __( 'You\'ve reached the limit for resending your account activation email. Please wait a few minutes and try again. If you continue to experience issues, contact support for assistance.', 'buddypress' ),
     825                array( 'status' => 500 )
    814826            );
    815827        }
     
    976988        $password = (string) $value;
    977989
    978         if ( empty( $password ) || false !== strpos( $password, '\\' ) ) {
     990        if ( empty( $password ) || str_contains( $password, '\\' ) ) {
    979991            return new WP_Error(
    980992                'rest_user_invalid_password',
  • trunk/src/bp-members/classes/class-bp-signup.php

    r13989 r14071  
    44 *
    55 * @package BuddyPress
    6  * @subpackage coreClasses
     6 * @subpackage Signup
    77 * @since 2.0.0
    88 */
     
    160160     * @since 2.0.0
    161161     *
    162      * @param integer $signup_id The ID for the signup being queried.
     162     * @param int $signup_id The ID for the signup being queried.
    163163     */
    164164    public function __construct( $signup_id = 0 ) {
     
    249249         * was sent in the last day.
    250250         */
    251         $this->recently_sent = $this->count_sent && ( $diff < 1 * DAY_IN_SECONDS );
    252 
     251        $this->recently_sent = $this->count_sent && ( $diff < DAY_IN_SECONDS );
    253252    }
    254253
     
    827826     *
    828827     * @since 2.0.0
    829      *
    830      * @param array $signup_ids Single ID or list of IDs to resend.
     828     * @since 15.0.0 Added the ability to resend to a single ID.
     829     *
     830     * @param array|int $signup_ids Single ID or list of IDs to resend.
    831831     * @return array
    832832     */
    833833    public static function resend( $signup_ids = array() ) {
    834         if ( empty( $signup_ids ) || ! is_array( $signup_ids ) ) {
    835             return false;
     834        if ( empty( $signup_ids ) ) {
     835            return array();
     836        }
     837
     838        if ( ! is_array( $signup_ids ) ) {
     839            $signup_ids = array( $signup_ids );
    836840        }
    837841
    838842        $to_resend = self::get(
    839843            array(
    840                 'include' => $signup_ids,
     844                'include' => wp_parse_id_list( $signup_ids ),
    841845            )
    842846        );
    843847
    844         if ( ! $signups = $to_resend['signups'] ) {
    845             return false;
     848        $signups = $to_resend['signups'];
     849
     850        if ( ! $signups ) {
     851            return array();
    846852        }
    847853
     
    876882                $user_id = email_exists( $signup->user_email );
    877883
    878                 if ( ! empty( $user_id ) && 2 != self::check_user_status( $user_id ) ) {
     884                if ( ! empty( $user_id ) && 2 !== self::check_user_status( $user_id ) ) {
    879885
    880886                    // Status is not 2, so user's account has been activated.
     
    886892                    continue;
    887893
    888                 // Send the validation email.
     894                    // Send the validation email.
    889895                } else {
    890896                    $salutation = $signup->user_login;
     
    907913
    908914        /**
    909          * Fires after activation emails are resent.
     915         * Fires after activation email(s) are/is resent.
    910916         *
    911917         * @since 2.0.0
     
    924930         */
    925931        return apply_filters( 'bp_core_signup_resend', $result );
     932    }
     933
     934    /**
     935     * Check if an activation email can be resent.
     936     *
     937     * @since 15.0.0
     938     *
     939     * @param BP_Signup $signup The signup object.
     940     * @return bool
     941     */
     942    public static function allow_activation_resend( $signup ) {
     943
     944        // Bail if the signup is not a BP_Signup object.
     945        if ( ! $signup instanceof BP_Signup ) {
     946            return false;
     947        }
     948
     949        // Allow the activation email to be sent if not already.
     950        if ( ! $signup->recently_sent || ! $signup->count_sent ) {
     951            return true;
     952        }
     953
     954        $sent_at = mysql2date( 'U', $signup->date_sent );
     955        $now     = time();
     956        $diff    = $now - $sent_at;
     957
     958        /**
     959         * Filters the lock time for the resend activation.
     960         *
     961         * @since 15.0.0
     962         *
     963         * @param float|int $lock_time The lock time for the resend activation. Default: 1 hour.
     964         * @param BP_Signup $signup The signup object.
     965         */
     966        $lock_time = apply_filters( 'bp_core_signup_resend_activation_lock_time', HOUR_IN_SECONDS, $signup );
     967
     968        // If the activation email was sent less than the lock time ago.
     969        return false === ( $diff < $lock_time );
    926970    }
    927971
  • trunk/tests/phpunit/testcases/members/class-bp-signup.php

    r14026 r14071  
    5151        );
    5252
    53         $signup = BP_Signup::add( $args );
     53        $signup = self::factory()->signup->create( $args );
    5454        $this->assertNotEmpty( $signup );
    5555
     
    7070
    7171        // Add new signup without a custom field visibility set for field_1.
    72         $signup = BP_Signup::add( array(
     72        $signup = self::factory()->signup->create( array(
    7373            'title' => 'Foo bar',
    7474            'user_login' => 'user1',
     
    484484            ),
    485485        );
    486         $s1 = BP_Signup::add( $args );
     486        $s1 = self::factory()->signup->create( $args );
    487487
    488488        $args['meta']['field_1'] = 'Fozz';
    489         $s2 = BP_Signup::add( $args );
     489        $s2 = self::factory()->signup->create( $args );
    490490
    491491        // Should find both.
     
    533533            ),
    534534        );
    535         $s1 = BP_Signup::add( $args );
     535        $s1 = self::factory()->signup->create( $args );
    536536
    537537        $args['meta']['field_1'] = 'Fozz';
    538         $s2 = BP_Signup::add( $args );
     538        $s2 = self::factory()->signup->create( $args );
    539539
    540540        // Should find both.
     
    637637    }
    638638
     639    /**
     640     * @group resend
     641     */
    639642    public function test_bp_core_signup_send_validation_email_should_increment_sent_count() {
    640643        $activation_key = wp_generate_password( 32, false );
    641644        $user_email     = 'accountone@example.com';
    642         $s1             = self::factory()->signup->create( array(
     645        $s1             = self::factory()->signup->create_and_get( array(
    643646            'user_login'     => 'accountone',
    644647            'user_email'     => $user_email,
     
    646649        ) );
    647650
    648         $signup = new BP_Signup( $s1 );
    649         $this->assertEquals( 0, $signup->count_sent );
     651        $this->assertEquals( 0, $s1->count_sent );
    650652
    651653        bp_core_signup_send_validation_email( 0, $user_email, $activation_key );
    652654
    653         $signup = new BP_Signup( $s1 );
     655        $signup = new BP_Signup( $s1->id );
    654656        $this->assertEquals( 1, $signup->count_sent );
    655657    }
     658
     659    /**
     660     * @ticket BP9137
     661     * @group resend
     662     */
     663    public function test_bp_core_signup_resend_email_activation() {
     664        $s1 = self::factory()->signup->create_and_get(
     665            array(
     666                'user_login'     => 'user' . wp_rand( 1, 20 ),
     667                'user_email'     => sprintf( 'user%d@example.com', wp_rand( 1, 20 ) ),
     668                'registered'     => bp_core_current_time(),
     669                'activation_key' => wp_generate_password( 32, false ),
     670                'meta'           => array(
     671                    'field_1' => 'Foo Bar',
     672                ),
     673            )
     674        );
     675
     676        BP_Signup::resend( $s1->id );
     677
     678        $this->assertFalse( BP_Signup::allow_activation_resend( 0 ) );
     679        $this->assertFalse( BP_Signup::allow_activation_resend( '' ) );
     680        $this->assertTrue( BP_Signup::allow_activation_resend( $s1 ) );
     681
     682        $s1->count_sent = 0;
     683        $this->assertTrue( BP_Signup::allow_activation_resend( $s1 ) );
     684
     685        $s1->count_sent = 1;
     686        $s1->recently_sent = true;
     687        $this->assertFalse( BP_Signup::allow_activation_resend( $s1 ) );
     688
     689        add_filter( 'bp_core_signup_resend_activation_lock_time', '__return_zero' );
     690        $this->assertTrue( BP_Signup::allow_activation_resend( $s1 ) );
     691        remove_filter( 'bp_core_signup_resend_activation_lock_time', '__return_zero' );
     692    }
    656693}
  • trunk/tests/phpunit/testcases/members/functions.php

    r14026 r14071  
    848848
    849849    /**
    850      * Provider for the test_bp_core_validate_user_signup() test.
     850     * Provider for the test_bp_core_validate_user_signup_errors() test.
    851851     *
    852852     * @return array[]
  • trunk/tests/phpunit/testcases/members/test-signup-controller.php

    r14070 r14071  
    210210     * @group create_item
    211211     */
     212    public function test_creating_multiple_pending_accounts_with_different_usernames() {
     213        $request = new WP_REST_Request( 'POST', $this->endpoint_url );
     214
     215        $params = $this->set_signup_data( array( 'user_login' => 'user1' ) );
     216        $request->set_body_params( $params );
     217        $request->set_param( 'context', 'edit' );
     218        $response = $this->server->dispatch( $request );
     219
     220        $this->assertEquals( 200, $response->get_status() );
     221
     222        $signup = $response->get_data();
     223
     224        $this->assertSame( $signup['user_login'], $params['user_login'] );
     225        $this->assertSame( $signup['user_email'], $params['user_email'] );
     226        $this->assertTrue( ! isset( $signup['activation_key'] ) );
     227
     228        // Test with the same email.
     229        $params = $this->set_signup_data( array( 'user_login' => 'user2' ) );
     230        $request->set_body_params( $params );
     231        $request->set_param( 'context', 'edit' );
     232        $response = $this->server->dispatch( $request );
     233
     234        $this->assertErrorResponse( 'bp_rest_signup_validation_failed', $response, 500, 'This user\'s email is already registered.' );
     235
     236        // Test with a different email.
     237        $params = $this->set_signup_data( array( 'user_login' => 'user2', 'user_email' => 'user2@example.com' ) );
     238        $request->set_body_params( $params );
     239        $request->set_param( 'context', 'edit' );
     240        $response = $this->server->dispatch( $request );
     241
     242        $this->assertEquals( 200, $response->get_status() );
     243    }
     244
     245    /**
     246     * @group create_item
     247     */
    212248    public function test_create_item_with_signup_fields() {
    213249        $g1 = $this->bp::factory()->xprofile_group->create();
     
    664700     * @group resend_item
    665701     */
    666     public function test_resend_acivation_email_to_active_signup() {
     702    public function test_resend_activation_email_to_active_signup() {
    667703        $signup_id = $this->create_signup();
    668704        $signup    = new BP_Signup( $signup_id );
     
    689725     * @group resend_item
    690726     */
     727    public function test_resend_activation_email_to_locked_signup() {
     728        $signup_id = $this->create_signup();
     729
     730        BP_Signup::resend( $signup_id );
     731
     732        $request = new WP_REST_Request( 'PUT', $this->endpoint_url . '/resend' );
     733        $request->set_param( 'id', $signup_id );
     734        $request->set_param( 'context', 'edit' );
     735        $response = $this->server->dispatch( $request );
     736
     737        $this->assertEquals( 500, $response->get_status() );
     738
     739        $error_code = 'bp_rest_signup_resend_activation_email_fail';
     740        $error      = $response->as_error();
     741        $message    = $error->get_error_message( $error_code );
     742
     743        $this->assertErrorResponse( $error_code, $response, 500 );
     744        $this->assertSame(
     745            $message,
     746            "You've reached the limit for resending your account activation email. Please wait a few minutes and try again. If you continue to experience issues, contact support for assistance."
     747        );
     748    }
     749
     750    /**
     751     * @group resend_item
     752     */
     753    public function test_resend_activation_email_to_locked_signup_with_hook() {
     754        $signup_id = $this->create_signup();
     755
     756        BP_Signup::resend( $signup_id );
     757
     758        add_filter( 'bp_core_signup_resend_activation_lock_time', '__return_zero' );
     759
     760        $request = new WP_REST_Request( 'PUT', $this->endpoint_url . '/resend' );
     761        $request->set_param( 'id', $signup_id );
     762        $request->set_param( 'context', 'edit' );
     763        $response = $this->server->dispatch( $request );
     764
     765        $this->assertEquals( 200, $response->get_status() );
     766
     767        $all_data = $response->get_data();
     768
     769        $this->assertTrue( $all_data['sent'] );
     770
     771        remove_filter( 'bp_core_signup_resend_activation_lock_time', '__return_zero' );
     772    }
     773
     774    /**
     775     * @group resend_item
     776     */
    691777    public function test_resend_activation_email_invalid_signup_id() {
    692778        $request = new WP_REST_Request( 'PUT', $this->endpoint_url . '/resend' );
     
    734820
    735821    protected function create_signup() {
    736         return BP_Signup::add(
     822        return $this->bp::factory()->signup->create(
    737823            array(
    738824                'user_login'     => 'user' . wp_rand( 1, 20 ),
     
    805891        $this->assertEquals( array( 'view', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
    806892    }
     893
     894    public function test_bp_rest_api_signup_disabled_feature_dispatch_error() {
     895        // Disable signups registration.
     896        bp_update_option( 'users_can_register', 0 );
     897
     898        if  ( is_multisite() ) {
     899            update_site_option( 'registration', '' );
     900        }
     901
     902        $request  = new WP_REST_Request( 'OPTIONS', $this->endpoint_url );
     903        $response = $this->server->dispatch( $request );
     904        $data     = $response->get_data();
     905
     906        $this->assertEquals( 403, $response->get_status() );
     907        $this->assertSame(
     908            $data['message'],
     909            'BuddyPress: The user signup feature is currently disabled. Please activate this feature to proceed.'
     910        );
     911    }
    807912}
Note: See TracChangeset for help on using the changeset viewer.