WordPress.org

Make WordPress Core


Ignore:
Timestamp:
11/09/2016 07:02:53 AM (4 years ago)
Author:
westonruter
Message:

Customize: Store modifying user ID with setting change written into changeset and restore current user when setting is being saved.

Restoring the current user context when saving a setting ensures filters apply as expected, such as Kses. When a user is not associated with a given setting change, continue to override capability to be exist when saving. Skip overwriting setting values in a changeset that have not changed, facilitating concurrent editing and amending a changeset by a user with fewer privileges.

See #30937.
Fixes #38705.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/customize/manager.php

    r39180 r39181  
    430430            'changeset_uuid' => $uuid,
    431431        ) );
     432        $wp_customize = $manager;
    432433        $manager->register_controls();
    433434        $manager->set_post_value( 'blogname', 'Changeset Title' );
    434435        $manager->set_post_value( 'blogdescription', 'Changeset Tagline' );
    435436
     437        $pre_saved_data = array(
     438            'blogname' => array(
     439                'value' => 'Overridden Changeset Title',
     440            ),
     441            'blogdescription' => array(
     442                'custom' => 'something',
     443            ),
     444        );
    436445        $r = $manager->save_changeset_post( array(
    437446            'status' => 'auto-draft',
    438447            'title' => 'Auto Draft',
    439448            'date_gmt' => '2010-01-01 00:00:00',
    440             'data' => array(
    441                 'blogname' => array(
    442                     'value' => 'Overridden Changeset Title',
    443                 ),
    444                 'blogdescription' => array(
    445                     'custom' => 'something',
    446                 ),
    447             ),
     449            'data' => $pre_saved_data,
    448450        ) );
    449451        $this->assertInternalType( 'array', $r );
     
    455457        $saved_data = json_decode( get_post( $post_id )->post_content, true );
    456458        $this->assertEquals( $manager->unsanitized_post_values(), wp_list_pluck( $saved_data, 'value' ) );
    457         $this->assertEquals( 'Overridden Changeset Title', $saved_data['blogname']['value'] );
    458         $this->assertEquals( 'something', $saved_data['blogdescription']['custom'] );
     459        $this->assertEquals( $pre_saved_data['blogname']['value'], $saved_data['blogname']['value'] );
     460        $this->assertEquals( $pre_saved_data['blogdescription']['custom'], $saved_data['blogdescription']['custom'] );
     461        foreach ( $saved_data as $setting_id => $setting_params ) {
     462            $this->assertArrayHasKey( 'type', $setting_params );
     463            $this->assertEquals( 'option', $setting_params['type'] );
     464            $this->assertArrayHasKey( 'user_id', $setting_params );
     465            $this->assertEquals( self::$admin_user_id, $setting_params['user_id'] );
     466        }
    459467        $this->assertEquals( 'Auto Draft', get_post( $post_id )->post_title );
    460468        $this->assertEquals( 'auto-draft', get_post( $post_id )->post_status );
     
    512520            'changeset_uuid' => $uuid,
    513521        ) );
     522        $wp_customize = $manager;
    514523        $manager->register_controls(); // That is, register settings.
    515524        $r = $manager->save_changeset_post( array(
     
    564573
    565574        $wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
    566         $manager->register_controls();
     575        do_action( 'customize_register', $wp_customize );
    567576        $manager->add_setting( 'scratchpad', array(
    568577            'type' => 'option',
     
    570579        ) );
    571580        $manager->get_setting( 'blogname' )->capability = 'exist';
     581        $original_capabilities = wp_list_pluck( $manager->settings(), 'capability' );
    572582        wp_set_current_user( self::$subscriber_user_id );
    573583        $r = $manager->save_changeset_post( array(
     
    585595        $this->assertEquals( 'Do it live \o/', get_option( 'blogname' ) );
    586596        $this->assertEquals( 'trash', get_post_status( $post_id ) ); // Auto-trashed.
     597        $this->assertEquals( $original_capabilities, wp_list_pluck( $manager->settings(), 'capability' ) );
    587598        $this->assertContains( '<script>', get_post( $post_id )->post_content );
    588599        $this->assertEquals( $manager->changeset_uuid(), get_post( $post_id )->post_name, 'Expected that the "__trashed" suffix to not be added.' );
     
    599610        $uuid = wp_generate_uuid4();
    600611        $wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
    601         $manager->register_controls();
     612        do_action( 'customize_register', $manager );
    602613
    603614        $manager->set_post_value( 'blogname', 'Hello Surface' );
     
    661672     */
    662673    function test_save_changeset_post_with_theme_activation() {
     674        global $wp_customize;
    663675        wp_set_current_user( self::$admin_user_id );
    664676
     
    677689            'theme' => $preview_theme,
    678690        ) );
    679         $manager->register_controls();
    680         $GLOBALS['wp_customize'] = $manager;
     691        $wp_customize = $manager;
     692        do_action( 'customize_register', $manager );
    681693
    682694        $manager->set_post_value( 'blogname', 'Hello Preview Theme' );
     
    687699        $this->assertEquals( $preview_theme, get_stylesheet() );
    688700        $this->assertEquals( 'Hello Preview Theme', get_option( 'blogname' ) );
     701    }
     702
     703    /**
     704     * Test saving changesets with varying users and capabilities.
     705     *
     706     * @ticket 38705
     707     * @covers WP_Customize_Manager::save_changeset_post()
     708     */
     709    function test_save_changeset_post_with_varying_users() {
     710        global $wp_customize;
     711
     712        add_theme_support( 'custom-background' );
     713        wp_set_current_user( self::$admin_user_id );
     714        $other_admin_user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
     715
     716        $uuid = wp_generate_uuid4();
     717        $manager = new WP_Customize_Manager( array(
     718            'changeset_uuid' => $uuid,
     719        ) );
     720        $wp_customize = $manager;
     721        do_action( 'customize_register', $manager );
     722        $manager->add_setting( 'scratchpad', array(
     723            'type' => 'option',
     724            'capability' => 'exist',
     725        ) );
     726
     727        // Create initial set of
     728        $r = $manager->save_changeset_post( array(
     729            'status' => 'auto-draft',
     730            'data' => array(
     731                'blogname' => array(
     732                    'value' => 'Admin 1 Title',
     733                ),
     734                'scratchpad' => array(
     735                    'value' => 'Admin 1 Scratch',
     736                ),
     737                'background_color' => array(
     738                    'value' => '#000000',
     739                ),
     740            ),
     741        ) );
     742        $this->assertInternalType( 'array', $r );
     743        $this->assertEquals(
     744            array_fill_keys( array( 'blogname', 'scratchpad', 'background_color' ), true ),
     745            $r['setting_validities']
     746        );
     747        $post_id = $manager->find_changeset_post_id( $uuid );
     748        $data = json_decode( get_post( $post_id )->post_content, true );
     749        $this->assertEquals( self::$admin_user_id, $data['blogname']['user_id'] );
     750        $this->assertEquals( self::$admin_user_id, $data['scratchpad']['user_id'] );
     751        $this->assertEquals( self::$admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
     752
     753        // Attempt to save just one setting under a different user.
     754        wp_set_current_user( $other_admin_user_id );
     755        $r = $manager->save_changeset_post( array(
     756            'status' => 'auto-draft',
     757            'data' => array(
     758                'blogname' => array(
     759                    'value' => 'Admin 2 Title',
     760                ),
     761                'background_color' => array(
     762                    'value' => '#FFFFFF',
     763                ),
     764            ),
     765        ) );
     766        $this->assertInternalType( 'array', $r );
     767        $this->assertEquals(
     768            array_fill_keys( array( 'blogname', 'background_color' ), true ),
     769            $r['setting_validities']
     770        );
     771        $data = json_decode( get_post( $post_id )->post_content, true );
     772        $this->assertEquals( 'Admin 2 Title', $data['blogname']['value'] );
     773        $this->assertEquals( $other_admin_user_id, $data['blogname']['user_id'] );
     774        $this->assertEquals( 'Admin 1 Scratch', $data['scratchpad']['value'] );
     775        $this->assertEquals( self::$admin_user_id, $data['scratchpad']['user_id'] );
     776        $this->assertEquals( '#FFFFFF', $data[ $this->manager->get_stylesheet() . '::background_color' ]['value'] );
     777        $this->assertEquals( $other_admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
     778
     779        // Attempt to save now as under-privileged user.
     780        $r = $manager->save_changeset_post( array(
     781            'status' => 'auto-draft',
     782            'data' => array(
     783                'scratchpad' => array(
     784                    'value' => 'Subscriber Scratch',
     785                ),
     786            ),
     787            'user_id' => self::$subscriber_user_id,
     788        ) );
     789        $this->assertInternalType( 'array', $r );
     790        $this->assertEquals(
     791            array_fill_keys( array( 'scratchpad' ), true ),
     792            $r['setting_validities']
     793        );
     794        $data = json_decode( get_post( $post_id )->post_content, true );
     795        $this->assertEquals( $other_admin_user_id, $data['blogname']['user_id'] );
     796        $this->assertEquals( self::$subscriber_user_id, $data['scratchpad']['user_id'] );
     797        $this->assertEquals( $other_admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
     798
     799        // Manually update the changeset so that the user_id context is not included.
     800        $data = json_decode( get_post( $post_id )->post_content, true );
     801        $data['blogdescription']['value'] = 'Programmatically-supplied Tagline';
     802        wp_update_post( wp_slash( array( 'ID' => $post_id, 'post_content' => wp_json_encode( $data ) ) ) );
     803
     804        // Ensure the modifying user set as the current user when each is saved, simulating WP Cron envronment.
     805        wp_set_current_user( 0 );
     806        $save_counts = array();
     807        foreach ( array_keys( $data ) as $setting_id ) {
     808            $setting_id = preg_replace( '/^.+::/', '', $setting_id );
     809            $save_counts[ $setting_id ] = did_action( sprintf( 'customize_save_%s', $setting_id ) );
     810        }
     811        $this->filtered_setting_current_user_ids = array();
     812        foreach ( $manager->settings() as $setting ) {
     813            add_filter( sprintf( 'customize_sanitize_%s', $setting->id ), array( $this, 'filter_customize_setting_to_log_current_user' ), 10, 2 );
     814        }
     815        wp_update_post( array( 'ID' => $post_id, 'post_status' => 'publish' ) );
     816        foreach ( array_keys( $data ) as $setting_id ) {
     817            $setting_id = preg_replace( '/^.+::/', '', $setting_id );
     818            $this->assertEquals( $save_counts[ $setting_id ] + 1, did_action( sprintf( 'customize_save_%s', $setting_id ) ), $setting_id );
     819        }
     820        $this->assertEqualSets( array( 'blogname', 'blogdescription', 'background_color', 'scratchpad' ), array_keys( $this->filtered_setting_current_user_ids ) );
     821        $this->assertEquals( $other_admin_user_id, $this->filtered_setting_current_user_ids['blogname'] );
     822        $this->assertEquals( 0, $this->filtered_setting_current_user_ids['blogdescription'] );
     823        $this->assertEquals( self::$subscriber_user_id, $this->filtered_setting_current_user_ids['scratchpad'] );
     824        $this->assertEquals( $other_admin_user_id, $this->filtered_setting_current_user_ids['background_color'] );
     825        $this->assertEquals( 'Subscriber Scratch', get_option( 'scratchpad' ) );
     826    }
     827
     828    /**
     829     * Test writing changesets and publishing with users who can unfiltered_html and those who cannot.
     830     *
     831     * @ticket 38705
     832     * @covers WP_Customize_Manager::save_changeset_post()
     833     */
     834    function test_save_changeset_post_with_varying_unfiltered_html_cap() {
     835        global $wp_customize;
     836        grant_super_admin( self::$admin_user_id );
     837        $this->assertTrue( user_can( self::$admin_user_id, 'unfiltered_html' ) );
     838        $this->assertFalse( user_can( self::$subscriber_user_id, 'unfiltered_html' ) );
     839        wp_set_current_user( 0 );
     840        add_action( 'customize_register', array( $this, 'register_scratchpad_setting' ) );
     841
     842        // Attempt scratchpad with user who has unfiltered_html.
     843        update_option( 'scratchpad', '' );
     844        $wp_customize = new WP_Customize_Manager();
     845        do_action( 'customize_register', $wp_customize );
     846        $wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
     847        $wp_customize->save_changeset_post( array(
     848            'status' => 'auto-draft',
     849            'user_id' => self::$admin_user_id,
     850        ) );
     851        $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ) );
     852        do_action( 'customize_register', $wp_customize );
     853        $wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
     854        $this->assertEquals( 'Unfiltered<script>evil</script>', get_option( 'scratchpad' ) );
     855
     856        // Attempt scratchpad with user who doesn't have unfiltered_html.
     857        update_option( 'scratchpad', '' );
     858        $wp_customize = new WP_Customize_Manager();
     859        do_action( 'customize_register', $wp_customize );
     860        $wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
     861        $wp_customize->save_changeset_post( array(
     862            'status' => 'auto-draft',
     863            'user_id' => self::$subscriber_user_id,
     864        ) );
     865        $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ) );
     866        do_action( 'customize_register', $wp_customize );
     867        $wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
     868        $this->assertEquals( 'Unfilteredevil', get_option( 'scratchpad' ) );
     869
     870        // Attempt publishing scratchpad as anonymous user when changeset was set by privileged user.
     871        update_option( 'scratchpad', '' );
     872        $wp_customize = new WP_Customize_Manager();
     873        do_action( 'customize_register', $wp_customize );
     874        $wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
     875        $wp_customize->save_changeset_post( array(
     876            'status' => 'auto-draft',
     877            'user_id' => self::$admin_user_id,
     878        ) );
     879        $changeset_post_id = $wp_customize->changeset_post_id();
     880        wp_set_current_user( 0 );
     881        $wp_customize = null;
     882        unset( $GLOBALS['wp_actions']['customize_register'] );
     883        $this->assertEquals( 'Unfilteredevil', apply_filters( 'content_save_pre', 'Unfiltered<script>evil</script>' ) );
     884        wp_publish_post( $changeset_post_id ); // @todo If wp_update_post() is used here, then kses will corrupt the post_content.
     885        $this->assertEquals( 'Unfiltered<script>evil</script>', get_option( 'scratchpad' ) );
     886    }
     887
     888    /**
     889     * Register scratchpad setting.
     890     *
     891     * @param WP_Customize_Manager $wp_customize Manager.
     892     */
     893    function register_scratchpad_setting( WP_Customize_Manager $wp_customize ) {
     894        $wp_customize->add_setting( 'scratchpad', array(
     895            'type' => 'option',
     896            'capability' => 'exist',
     897            'sanitize_callback' => array( $this, 'filter_sanitize_scratchpad' ),
     898        ) );
     899    }
     900
     901    /**
     902     * Sanitize scratchpad as if it is post_content so kses filters apply.
     903     *
     904     * @param string $value Value.
     905     * @return string Value.
     906     */
     907    function filter_sanitize_scratchpad( $value ) {
     908        return apply_filters( 'content_save_pre', $value );
     909    }
     910
     911    /**
     912     * Current user when settings are filtered.
     913     *
     914     * @var array
     915     */
     916    protected $filtered_setting_current_user_ids = array();
     917
     918    /**
     919     * Filter setting to capture the current user when the filter applies.
     920     *
     921     * @param mixed                $value   Setting value.
     922     * @param WP_Customize_Setting $setting Setting.
     923     * @return mixed Value.
     924     */
     925    function filter_customize_setting_to_log_current_user( $value, $setting ) {
     926        $this->filtered_setting_current_user_ids[ $setting->id ] = get_current_user_id();
     927        return $value;
    689928    }
    690929
Note: See TracChangeset for help on using the changeset viewer.