Plugin Directory

Changeset 3444373


Ignore:
Timestamp:
01/21/2026 07:32:42 PM (2 months ago)
Author:
wiredimpact
Message:
  • Fixed vulnerability where unauthenticated volunteer form submissions could overwrite first name, last name and phone number data for non-volunteer roles.
Location:
wired-impact-volunteer-management/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • wired-impact-volunteer-management/trunk/README.txt

    r3429767 r3444373  
    55Tested up to: 6.9
    66Requires PHP: 5.2.4
    7 Stable tag: 2.8
     7Stable tag: 2.8.1
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    127127== Changelog ==
    128128
     129= 2.8.1 =
     130* Fixed vulnerability where unauthenticated volunteer form submissions could overwrite first name, last name and phone number data for non-volunteer roles.
     131
    129132= 2.8 =
    130133* Updated Cypress tests to work with WordPress 6.9.
  • wired-impact-volunteer-management/trunk/cypress/support/commands.js

    r3242201 r3444373  
    4646    cy.exec(Cypress.env('wpCLIPrefix') + ' post delete $(' + Cypress.env('wpCLIPrefix') + ' post list --post_type=volunteer_opp --format=ids) --force');
    4747    cy.exec(Cypress.env('wpCLIPrefix') + ' post create --post_type=volunteer_opp --post_title="Clean up Trash" --post_status="publish"').then((result) => {
    48        
     48
    4949        let stdOutParts = result.stdout.slice(0, -1);
    5050        stdOutParts     = stdOutParts.split(' ');
     
    7979 */
    8080Cypress.Commands.add('trashAllGravityFormsForms', () => {
    81    
     81
    8282    cy.exec(Cypress.env('wpCLIPrefix') + ' gf form delete $(' + Cypress.env('wpCLIPrefix') + ' gf form list --format=ids)');
    8383});
     
    9494
    9595    const formJSON = {"fields":[{"type":"name","id":1,"formId":6,"label":"Name","adminLabel":"","isRequired":false,"size":"large","errorMessage":"","visibility":"visible","nameFormat":"advanced","inputs":[{"id":"1.2","label":"Prefix","name":"","autocompleteAttribute":"honorific-prefix","choices":[{"text":"Dr.","value":"Dr."},{"text":"Miss","value":"Miss"},{"text":"Mr.","value":"Mr."},{"text":"Mrs.","value":"Mrs."},{"text":"Ms.","value":"Ms."},{"text":"Mx.","value":"Mx."},{"text":"Prof.","value":"Prof."},{"text":"Rev.","value":"Rev."}],"isHidden":true,"inputType":"radio"},{"id":"1.3","label":"First","name":"","autocompleteAttribute":"given-name"},{"id":"1.4","label":"Middle","name":"","autocompleteAttribute":"additional-name","isHidden":true},{"id":"1.6","label":"Last","name":"","autocompleteAttribute":"family-name"},{"id":"1.8","label":"Suffix","name":"","autocompleteAttribute":"honorific-suffix","isHidden":true}],"description":"","allowsPrepopulate":false,"inputMask":false,"inputMaskValue":"","inputMaskIsCustom":"","maxLength":"","inputType":"","labelPlacement":"","descriptionPlacement":"","subLabelPlacement":"","placeholder":"","cssClass":"","inputName":"","noDuplicates":false,"defaultValue":"","enableAutocomplete":false,"autocompleteAttribute":"","choices":"","conditionalLogic":"","productField":"","layoutGridColumnSpan":"","enableEnhancedUI":0,"layoutGroupId":"b7373b44","fields":"","displayOnly":""},{"type":"phone","id":3,"formId":6,"label":"Phone","adminLabel":"","isRequired":false,"size":"large","errorMessage":"","visibility":"visible","inputs":null,"phoneFormat":"international","autocompleteAttribute":"tel","description":"","allowsPrepopulate":false,"inputMask":false,"inputMaskValue":"","inputMaskIsCustom":"","maxLength":"","inputType":"","labelPlacement":"","descriptionPlacement":"","subLabelPlacement":"","placeholder":"","cssClass":"","inputName":"","noDuplicates":false,"defaultValue":"","enableAutocomplete":false,"choices":"","conditionalLogic":"","productField":"","layoutGridColumnSpan":12,"enableEnhancedUI":0,"layoutGroupId":"33f7781e","fields":"","displayOnly":""},{"type":"email","id":4,"formId":6,"label":"Email","adminLabel":"","isRequired":false,"size":"large","errorMessage":"","visibility":"visible","inputs":null,"autocompleteAttribute":"email","description":"","allowsPrepopulate":false,"inputMask":false,"inputMaskValue":"","inputMaskIsCustom":"","maxLength":"","inputType":"","labelPlacement":"","descriptionPlacement":"","subLabelPlacement":"","placeholder":"","cssClass":"","inputName":"","noDuplicates":false,"defaultValue":"","enableAutocomplete":false,"choices":"","conditionalLogic":"","productField":"","layoutGridColumnSpan":12,"emailConfirmEnabled":"","enableEnhancedUI":0,"layoutGroupId":"9467c1ea","fields":"","displayOnly":""},{"type":"select","id":6,"formId":6,"label":"T-Shirt Size","adminLabel":"","isRequired":false,"size":"large","errorMessage":"","visibility":"visible","validateState":true,"inputs":null,"choices":[{"text":"Extra Small","value":"Extra Small","isSelected":false,"price":""},{"text":"Small","value":"Small","isSelected":false,"price":""},{"text":"Medium","value":"Medium","isSelected":false,"price":""},{"text":"Large","value":"Large","isSelected":false,"price":""},{"text":"Extra Large","value":"Extra Large","isSelected":false,"price":""}],"description":"","allowsPrepopulate":false,"inputMask":false,"inputMaskValue":"","inputMaskIsCustom":false,"maxLength":"","inputType":"","labelPlacement":"","descriptionPlacement":"","subLabelPlacement":"","placeholder":"","cssClass":"","inputName":"","noDuplicates":false,"defaultValue":"","enableAutocomplete":false,"autocompleteAttribute":"","conditionalLogic":"","productField":"","layoutGridColumnSpan":12,"enablePrice":"","enableEnhancedUI":0,"layoutGroupId":"73e95759","multipleFiles":false,"maxFiles":"","calculationFormula":"","calculationRounding":"","enableCalculation":"","disableQuantity":false,"displayAllCategories":false,"useRichTextEditor":false,"errors":[],"fields":""},{"type":"textarea","id":5,"formId":6,"label":"Why do you want to volunteer with us?","adminLabel":"","isRequired":false,"size":"medium","errorMessage":"","visibility":"visible","inputs":null,"description":"","allowsPrepopulate":false,"inputMask":false,"inputMaskValue":"","inputMaskIsCustom":false,"maxLength":"","inputType":"","labelPlacement":"","descriptionPlacement":"","subLabelPlacement":"","placeholder":"","cssClass":"","inputName":"","noDuplicates":false,"defaultValue":"","enableAutocomplete":false,"autocompleteAttribute":"","choices":"","conditionalLogic":"","productField":"","layoutGridColumnSpan":12,"form_id":"","useRichTextEditor":false,"enableEnhancedUI":0,"layoutGroupId":"a96865a7","multipleFiles":false,"maxFiles":"","calculationFormula":"","calculationRounding":"","enableCalculation":"","disableQuantity":false,"displayAllCategories":false,"errors":[],"fields":""}],"button":{"type":"text","text":"","imageUrl":"","width":"auto","location":"bottom","layoutGridColumnSpan":12},"title":"Volunteer Signup","description":"","version":"2.7.17.1","id":6,"markupVersion":2,"nextFieldId":7,"useCurrentUserAsAuthor":true,"postContentTemplateEnabled":false,"postTitleTemplateEnabled":false,"postTitleTemplate":"","postContentTemplate":"","lastPageButton":null,"pagination":null,"firstPageCssClass":null,"confirmations":[{"id":"65734ead4e58d","name":"Default Confirmation","isDefault":true,"type":"message","message":"Thanks for signing up to volunteer!","url":"","pageId":"","queryString":"","event":"","disableAutoformat":false,"page":"","conditionalLogic":[]}],"notifications":[{"id":"65734ead4e18f","isActive":true,"to":"{admin_email}","name":"Admin Notification","event":"form_submission","toType":"email","subject":"New submission from {form_title}","message":"{all_fields}"}]};
    96    
     96
    9797    cy.exec(Cypress.env('wpCLIPrefix') + ' gf form create "Volunteer Signup" "" --form-json=\'' + JSON.stringify(formJSON) + '\'').then((result) => {
    98        
     98
    9999        const stdOutParts = result.stdout.split(' ');
    100100        const formID      = stdOutParts[stdOutParts.length - 1];
     
    114114 */
    115115Cypress.Commands.add('deleteAllMailCatcherEmails', () => {
    116    
     116
    117117    cy.request('DELETE', 'http://127.0.0.1:1080/messages').then((response) => {
    118118        expect(response.status).to.eq(204);
     
    121121
    122122/**
    123  * Get the WordPress iframe content editor so we can test inside of it.
     123 * Get the WordPress block editor content area.
     124 *
     125 * In WordPress 6.3+, the post editor may or may not use an iframe depending
     126 * on theme type and other conditions. This command handles both cases.
    124127 *
    125128 * You must also set chromeWebSecurity to false in cypress.config.js
    126129 * to work with iframes.
    127  *
     130 *
     131 * @see https://make.wordpress.org/core/2023/07/18/miscellaneous-editor-changes-in-wordpress-6-3/
    128132 * @see https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/blogs__iframes
    129  * @see https://on.cypress.io/wrap
    130133 */
    131134Cypress.Commands.add('getBlockEditorIFrameBody', () => {
     
    133136    cy.log('getBlockEditorIFrameBody');
    134137
    135     return cy
    136         .get('iframe[name="editor-canvas"]', { log: false })
    137         .its('0.contentDocument.body', { log: false }).should('not.be.empty')
    138         .then((body) => cy.wrap(body, { log: false }))
     138    return cy.get('body').then(($body) => {
     139        const $iframe = $body.find('iframe[name="editor-canvas"]');
     140
     141        if ($iframe.length) {
     142            // Editor is iframed
     143            return cy
     144                .get('iframe[name="editor-canvas"]', { log: false })
     145                .its('0.contentDocument.body', { log: false }).should('not.be.empty')
     146                .then((body) => cy.wrap(body, { log: false }));
     147        } else {
     148            // Editor is not iframed
     149            return cy.get('.editor-styles-wrapper', { log: false });
     150        }
     151    });
    139152});
  • wired-impact-volunteer-management/trunk/includes/class-volunteer.php

    r3008933 r3444373  
    7070     */
    7171    public function set_meta(){
    72         $user_data = get_userdata( $this->ID );
     72        $user_data  = get_userdata( $this->ID );
    7373        $this->meta = array(
    74             'first_name'                => $user_data->first_name,
    75             'last_name'                 => $user_data->last_name,
    76             'email'                     => $user_data->user_email,
    77             'phone'                     => $this->format_phone_number( get_user_option( 'phone', $this->ID ) ),
    78             'notes'                     => esc_textarea( get_user_option( 'notes', $this->ID ) ),
    79             'num_volunteer_opps'        => $this->get_num_volunteer_opps(),
    80             'first_volunteer_opp_time'  => $this->get_first_volunteer_opp_time()
     74            'first_name'               => $user_data->first_name,
     75            'last_name'                => $user_data->last_name,
     76            'email'                    => $user_data->user_email,
     77            'phone'                    => $this->format_phone_number( get_user_option( 'phone', $this->ID ) ),
     78            'notes'                    => esc_textarea( get_user_option( 'notes', $this->ID ) ),
     79            'num_volunteer_opps'       => $this->get_num_volunteer_opps(),
     80            'first_volunteer_opp_time' => $this->get_first_volunteer_opp_time()
    8181        );
    8282    }
     
    8686     *
    8787     * @todo   Remove duplicates of this method that exist in other classes
    88      * 
    89      * @param  int $unformmated_number Phone number in only integers
     88     *
     89     * @param  int $unformatted_number Phone number in only integers.
    9090     * @return string Phone number formatted to look nice.
    9191     */
    92     public function format_phone_number( $unformatted_number ){
     92    public function format_phone_number( $unformatted_number ) {
    9393        $formatted_number = '';
    9494
     
    107107    /**
    108108     * Get the number of volunteer opportunities this volunteer has signed up for.
    109      * 
     109     *
    110110     * @return int Number of volunteer opportunities signed up for
    111111     */
     
    130130     * This is used to display the year the person started volunteering within the admin. It's worth
    131131     * noting that this isn't the time of the opportunity, but rather when they RSVPed.
    132      * 
     132     *
    133133     * @return string The date and time of the first volunteer RSVP.
    134134     */
     
    151151     * Get an array with all of the volunteer opportunities this person has signed up for.
    152152     *
    153      * First we pull only the IDs of the posts that have been signed up for with the most recent one they signed 
     153     * First we pull only the IDs of the posts that have been signed up for with the most recent one they signed
    154154     * up for first. Then we create a new WI_Volunteer_Management_Opportunity object for each and return it.
    155155     *
     
    163163
    164164        switch( $type ){
    165             //All Volunteer Opportunities
     165            // All Volunteer Opportunities.
    166166            case 'all':
    167 
    168167                $query = "
    169168                         SELECT post_id
     
    174173
    175174                $query_values = array( $this->ID, 1 );
    176                        
     175
    177176                break;
    178177
    179             //One-Time Volunteer Opportunities
    180             //For this query we joined the postmeta table on itself in order to use two meta values.
     178            // One-Time Volunteer Opportunities.
     179            // For this query we joined the postmeta table on itself in order to use two meta values.
    181180            case 'one-time':
    182 
    183181                $query = "
    184182                         SELECT rsvps.post_id
     
    196194                break;
    197195
    198             //Flexible Volunteer Opportunities
     196            // Flexible Volunteer Opportunities.
    199197            case 'flexible':
    200 
    201198                $query = "
    202199                         SELECT rsvps.post_id
     
    214211        $volunteer_opps = $wpdb->get_results( $wpdb->prepare( $query, $query_values ) );
    215212
    216         //Use post id to grab a bunch info on each opportunity and store in the same variable using &.
    217         foreach( $volunteer_opps as &$opp ){
     213        // Use post id to grab a bunch info on each opportunity and store in the same variable using &.
     214        foreach ( $volunteer_opps as &$opp ) {
    218215            $opp = new WI_Volunteer_Management_Opportunity( $opp->post_id );
    219216        }
     
    224221    /**
    225222     * Remove an RSVP for a user for a specific volunteer opportunity. This is usually done through AJAX.
    226      * 
    227      * @param  int $post_id ID of the volunteer opportunity to have its RSVP removed
     223     *
     224     * @param  int $post_id ID of the volunteer opportunity to have its RSVP removed.
    228225     * @return int|bool The number of rows updated or false if error
    229226     */
     
    231228        global $wpdb;
    232229
    233         $status = $wpdb->update( 
    234             $wpdb->prefix  . 'volunteer_rsvps', 
     230        $status = $wpdb->update(
     231            $wpdb->prefix  . 'volunteer_rsvps',
    235232            array( //Data to update
    236233                'rsvp' => 0
    237             ), 
     234            ),
    238235            array( //Where
    239236                'user_id' => $this->ID,
    240237                'post_id' => $post_id
    241             ), 
     238            ),
    242239            array( '%d' ), //Data formats
    243240            array( '%d', '%d' ) //Where formats
     
    253250     * volunteer including the contact info, notes on them and which volunteer opportunities they signed up for.
    254251     *
    255      * @param int $user_id The volunteer's ID.
    256252     * @return string The URL needed to view this volunteer's information.
    257253     */
     
    287283            $user_id = wp_insert_user( $userdata );
    288284
    289         } else { // If the user already exists, update the user based on their email address.
    290 
    291             $userdata['ID'] = $existing_user;
    292 
    293             $user_id = wp_update_user( $userdata );
     285            // Update phone for new users.
     286            update_user_option( $user_id, 'phone', preg_replace( '/[^0-9]/', '', $form_fields['wivm_phone'] ) );
     287
     288        } else {
     289
     290            // User already exists: only update their data if they have the volunteer role.
     291            // This prevents unauthenticated users from overwriting data for admins or other roles.
     292            $user_id       = $existing_user;
     293            $should_update = $this->should_update_existing_user( $existing_user );
     294
     295            if ( $should_update ) {
     296
     297                $userdata['ID'] = $existing_user;
     298                wp_update_user( $userdata );
     299                update_user_option( $user_id, 'phone', preg_replace( '/[^0-9]/', '', $form_fields['wivm_phone'] ) );
     300            }
    294301
    295302            // On multisite we need to add the user to this site if they don't have access.
    296             if ( is_multisite() && ! is_user_member_of_blog( $userdata['ID'] ) ) {
    297 
    298                 add_user_to_blog( get_current_blog_id(), $userdata['ID'], 'volunteer' );
    299                 update_user_option( $userdata['ID'], 'notes', '' );
     303            if ( is_multisite() && ! is_user_member_of_blog( $existing_user ) ) {
     304
     305                add_user_to_blog( get_current_blog_id(), $existing_user, 'volunteer' );
     306                update_user_option( $existing_user, 'notes', '' );
    300307            }
    301308        }
    302309
    303         // Update custom user meta for new and existing volunteers.
    304         update_user_option( $user_id, 'phone', preg_replace( '/[^0-9]/', '', $form_fields['wivm_phone'] ) );
    305 
    306310        $this->ID = $user_id;
    307311
     
    310314
    311315    /**
     316     * Check if an existing user's data should be updated after volunteer form submission.
     317     *
     318     * Only users with the "volunteer" role should have their data updated.
     319     * This protects admin accounts and other user types from having their
     320     * data overwritten by unauthenticated form submissions.
     321     *
     322     * @param int $user_id The ID of the existing user.
     323     * @return bool True if the user's data should be updated, false otherwise.
     324     */
     325    private function should_update_existing_user( $user_id ) {
     326
     327        $user = get_userdata( $user_id );
     328
     329        if ( ! $user ) {
     330
     331            return false;
     332        }
     333
     334        // Only allow updates for users with the volunteer role.
     335        $should_update = in_array( 'volunteer', (array) $user->roles, true );
     336
     337        /**
     338         * Filter whether an existing user's data should be updated after volunteer form submission.
     339         *
     340         * @param bool    $should_update Whether the user's data should be updated.
     341         * @param int     $user_id       The ID of the existing user.
     342         * @param WP_User $user          The user object.
     343         */
     344        return apply_filters( 'wivm_should_update_existing_volunteer', $should_update, $user_id, $user );
     345    }
     346
     347    /**
    312348     * Delete RSVPs for this user.
    313349     *
    314350     * This is typically done right before the user is deleted from WordPress entirely.
    315      * 
     351     *
    316352     * @return int|bool Int for number of rows updated of false on error
    317353     */
    318     public function delete_rsvps(){
     354    public function delete_rsvps() {
    319355        global $wpdb;
    320356
    321357        $delete_info = $wpdb->delete(
    322                 $wpdb->prefix  . "volunteer_rsvps",
    323                 array( 'user_id' => $this->ID ),
    324                 array( '%d' )
     358            $wpdb->prefix . 'volunteer_rsvps',
     359            array( 'user_id' => $this->ID ),
     360            array( '%d' )
    325361        );
    326362
    327363        return $delete_info;
    328364    }
    329 
    330365} //class WI_Volunteer_Management_Volunteer
  • wired-impact-volunteer-management/trunk/includes/class-wi-volunteer-management.php

    r3429767 r3444373  
    6969
    7070        $this->plugin_name = 'wired-impact-volunteer-management';
    71         $this->version     = '2.8';
     71        $this->version     = '2.8.1';
    7272
    7373        $this->load_dependencies();
  • wired-impact-volunteer-management/trunk/languages/wired-impact-volunteer-management.pot

    r3429767 r3444373  
    1 # Copyright (C) 2025 Wired Impact
     1# Copyright (C) 2026 Wired Impact
    22# This file is distributed under the GPL-2.0+.
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Wired Impact Volunteer Management 2.8\n"
     5"Project-Id-Version: Wired Impact Volunteer Management 2.8.1\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wired-impact-volunteer-management\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2025-12-30T18:09:42+00:00\n"
     12"POT-Creation-Date: 2026-01-21T17:56:37+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.11.0\n"
  • wired-impact-volunteer-management/trunk/wivm.php

    r3429767 r3444373  
    1717 * Plugin URI:        https://wiredimpact.com/wordpress-plugins-for-nonprofits/volunteer-management/
    1818 * Description:       A simple, free way to keep track of your nonprofit’s volunteers and opportunities.
    19  * Version:           2.8
     19 * Version:           2.8.1
    2020 * Requires at least: 6.3
    2121 * Requires PHP:      5.2.4
Note: See TracChangeset for help on using the changeset viewer.