WordPress.org

Make WordPress Core

Changeset 43082


Ignore:
Timestamp:
05/02/2018 12:48:23 AM (14 months ago)
Author:
SergeyBiryukov
Message:

Privacy: add helper function for anonymizing data in a standardized way.

Props jesperher, allendav, iandunn, birgire, azaozz, joemcgill.
Merges [42971] and [43081] to the 4.9 branch.
Fixes #43545.

Location:
branches/4.9
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • branches/4.9

  • branches/4.9/src/wp-admin/includes/class-wp-community-events.php

    r42016 r43082  
    235235    public static function get_unsafe_client_ip() {
    236236        $client_ip = $netmask = false;
    237         $ip_prefix = '';
    238237
    239238        // In order of preference, with the best ones for this purpose first.
     
    266265        }
    267266
    268         // Detect what kind of IP address this is.
    269         $is_ipv6 = substr_count( $client_ip, ':' ) > 1;
    270         $is_ipv4 = ( 3 === substr_count( $client_ip, '.' ) );
    271 
    272         if ( $is_ipv6 && $is_ipv4 ) {
    273             // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4.
    274             $ip_prefix = '::ffff:';
    275             $client_ip = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $client_ip );
    276             $client_ip = str_replace( ']', '', $client_ip );
    277             $is_ipv6   = false;
    278         }
    279 
    280         if ( $is_ipv6 ) {
    281             // IPv6 addresses will always be enclosed in [] if there's a port.
    282             $ip_start = 1;
    283             $ip_end   = (int) strpos( $client_ip, ']' ) - 1;
    284             $netmask  = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
    285 
    286             // Strip the port (and [] from IPv6 addresses), if they exist.
    287             if ( $ip_end > 0 ) {
    288                 $client_ip = substr( $client_ip, $ip_start, $ip_end );
    289             }
    290 
    291             // Partially anonymize the IP by reducing it to the corresponding network ID.
    292             if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
    293                 $client_ip = inet_ntop( inet_pton( $client_ip ) & inet_pton( $netmask ) );
    294             }
    295         } elseif ( $is_ipv4 ) {
    296             // Strip any port and partially anonymize the IP.
    297             $last_octet_position = strrpos( $client_ip, '.' );
    298             $client_ip           = substr( $client_ip, 0, $last_octet_position ) . '.0';
    299         } else {
     267        $anon_ip = wp_privacy_anonymize_ip( $client_ip, true );
     268
     269        if ( '0.0.0.0' === $anon_ip || '::' === $anon_ip ) {
    300270            return false;
    301271        }
    302272
    303         // Restore the IPv6 prefix to compatibility mode addresses.
    304         return $ip_prefix . $client_ip;
     273        return $anon_ip;
    305274    }
    306275
  • branches/4.9/src/wp-includes/functions.php

    r42811 r43082  
    58175817    ), $email_change_email['message'], $email_change_email['headers'] );
    58185818}
     5819
     5820/**
     5821 * Return an anonymized IPv4 or IPv6 address.
     5822 *
     5823 * @since 4.9.6 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`.
     5824 *
     5825 * @param  string $ip_addr        The IPv4 or IPv6 address to be anonymized.
     5826 * @param  bool   $ipv6_fallback  Optional. Whether to return the original IPv6 address if the needed functions
     5827 *                                to anonymize it are not present. Default false, return `::` (unspecified address).
     5828 * @return string  The anonymized IP address.
     5829 */
     5830function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) {
     5831    // Detect what kind of IP address this is.
     5832    $ip_prefix = '';
     5833    $is_ipv6   = substr_count( $ip_addr, ':' ) > 1;
     5834    $is_ipv4   = ( 3 === substr_count( $ip_addr, '.' ) );
     5835
     5836    if ( $is_ipv6 && $is_ipv4 ) {
     5837        // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4.
     5838        $ip_prefix = '::ffff:';
     5839        $ip_addr   = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr );
     5840        $ip_addr   = str_replace( ']', '', $ip_addr );
     5841        $is_ipv6   = false;
     5842    }
     5843
     5844    if ( $is_ipv6 ) {
     5845        // IPv6 addresses will always be enclosed in [] if there's a port.
     5846        $left_bracket  = strpos( $ip_addr, '[' );
     5847        $right_bracket = strpos( $ip_addr, ']' );
     5848        $percent       = strpos( $ip_addr, '%' );
     5849        $netmask       = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
     5850
     5851        // Strip the port (and [] from IPv6 addresses), if they exist.
     5852        if ( false !== $left_bracket && false !== $right_bracket ) {
     5853            $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 );
     5854        } elseif ( false !== $left_bracket || false !== $right_bracket ) {
     5855            // The IP has one bracket, but not both, so it's malformed.
     5856            return '::';
     5857        }
     5858
     5859        // Strip the reachability scope.
     5860        if ( false !== $percent ) {
     5861            $ip_addr = substr( $ip_addr, 0, $percent );
     5862        }
     5863
     5864        // No invalid characters should be left.
     5865        if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) {
     5866            return '::';
     5867        }
     5868
     5869        // Partially anonymize the IP by reducing it to the corresponding network ID.
     5870        if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
     5871            $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) );
     5872            if ( false === $ip_addr) {
     5873                return '::';
     5874            }
     5875        } elseif ( ! $ipv6_fallback ) {
     5876            return '::';
     5877        }
     5878    } elseif ( $is_ipv4 ) {
     5879        // Strip any port and partially anonymize the IP.
     5880        $last_octet_position = strrpos( $ip_addr, '.' );
     5881        $ip_addr             = substr( $ip_addr, 0, $last_octet_position ) . '.0';
     5882    } else {
     5883        return '0.0.0.0';
     5884    }
     5885
     5886    // Restore the IPv6 prefix to compatibility mode addresses.
     5887    return $ip_prefix . $ip_addr;
     5888}
     5889
     5890/**
     5891 * Return uniform "anonymous" data by type.
     5892 *
     5893 * @since 4.9.6
     5894 *
     5895 * @param  string $type The type of data to be anonymized.
     5896 * @param  string $data Optional The data to be anonymized.
     5897 * @return string The anonymous data for the requested type.
     5898 */
     5899function wp_privacy_anonymize_data( $type, $data = '' ) {
     5900
     5901    switch ( $type ) {
     5902        case 'email':
     5903            $anonymous = 'deleted@site.invalid';
     5904            break;
     5905        case 'url':
     5906            $anonymous = 'https://site.invalid';
     5907            break;
     5908        case 'ip':
     5909            $anonymous = wp_privacy_anonymize_ip( $data );
     5910            break;
     5911        case 'date':
     5912            $anonymous = '0000-00-00 00:00:00';
     5913            break;
     5914        case 'text':
     5915            /* translators: deleted text */
     5916            $anonymous = __( '[deleted]' );
     5917            break;
     5918        case 'longtext':
     5919            /* translators: deleted long text */
     5920            $anonymous = __( 'This content was deleted by the author.' );
     5921            break;
     5922        default:
     5923            $anonymous = '';
     5924    }
     5925
     5926    /**
     5927     * Filters the anonymous data for each type.
     5928     *
     5929     * @since 4.9.6
     5930     *
     5931     * @param string $anonymous Anonymized data.
     5932     * @param string $type      Type of the data.
     5933     * @param string $data      Original data.
     5934     */
     5935    return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
     5936}
  • branches/4.9/tests/phpunit/tests/admin/includesCommunityEvents.php

    r42016 r43082  
    260260     * Test that get_unsafe_client_ip() properly anonymizes all possible address formats
    261261     *
    262      * @dataProvider data_get_unsafe_client_ip_anonymization
     262     * @dataProvider data_get_unsafe_client_ip
    263263     *
    264264     * @ticket 41083
    265265     */
    266     public function test_get_unsafe_client_ip_anonymization( $raw_ip, $expected_result ) {
    267         $_SERVER['REMOTE_ADDR'] = $raw_ip;
    268         $actual_result          = WP_Community_Events::get_unsafe_client_ip();
     266    public function test_get_unsafe_client_ip( $raw_ip, $expected_result ) {
     267        $_SERVER['REMOTE_ADDR']    = 'this should not be used';
     268        $_SERVER['HTTP_CLIENT_IP'] = $raw_ip;
     269        $actual_result             = WP_Community_Events::get_unsafe_client_ip();
    269270
    270271        $this->assertEquals( $expected_result, $actual_result );
    271272    }
    272273
    273     public function data_get_unsafe_client_ip_anonymization() {
    274         return array(
    275             // Invalid IP.
     274    /**
     275     * Provide test cases for `test_get_unsafe_client_ip()`.
     276     *
     277     * @return array
     278     */
     279    public function data_get_unsafe_client_ip() {
     280        return array(
     281            // Handle '::' returned from `wp_privacy_anonymize_ip()`.
    276282            array(
    277                 '',    // Raw IP address
    278                 false, // Expected result
    279             ),
    280             // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     283                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001',
     284                false,
     285            ),
     286
     287            // Handle '0.0.0.0' returned from `wp_privacy_anonymize_ip()`.
    281288            array(
    282289                'unknown',
    283290                false,
    284291            ),
    285             // IPv4, no port
     292
     293            // Valid IPv4.
    286294            array(
    287                 '10.20.30.45',
    288                 '10.20.30.0',
    289             ),
    290             // IPv4, port
    291             array(
    292                 '10.20.30.45:20000',
    293                 '10.20.30.0',
    294             ),
    295             // IPv6, no port
     295                '198.143.164.252',
     296                '198.143.164.0',
     297            ),
     298
     299            // Valid IPv6.
    296300            array(
    297301                '2a03:2880:2110:df07:face:b00c::1',
    298302                '2a03:2880:2110:df07::',
    299303            ),
    300             // IPv6, port
    301             array(
    302                 '[2a03:2880:2110:df07:face:b00c::1]:20000',
    303                 '2a03:2880:2110:df07::',
    304             ),
    305             // IPv6, no port, reducible representation
    306             array(
    307                 '0000:0000:0000:0000:0000:0000:0000:0001',
    308                 '::',
    309             ),
    310             // IPv6, no port, partially reducible representation
    311             array(
    312                 '1000:0000:0000:0000:0000:0000:0000:0001',
    313                 '1000::',
    314             ),
    315             // IPv6, port, reducible representation
    316             array(
    317                 '[0000:0000:0000:0000:0000:0000:0000:0001]:1234',
    318                 '::',
    319             ),
    320             // IPv6, port, partially reducible representation
    321             array(
    322                 '[1000:0000:0000:0000:0000:0000:0000:0001]:5678',
    323                 '1000::',
    324             ),
    325             // IPv6, no port, reduced representation
    326             array(
    327                 '::',
    328                 '::',
    329             ),
    330             // IPv6, no port, reduced representation
    331             array(
    332                 '::1',
    333                 '::',
    334             ),
    335             // IPv6, port, reduced representation
    336             array(
    337                 '[::]:20000',
    338                 '::',
    339             ),
    340             // IPv6, address brackets without port delimiter and number, reduced representation
    341             array(
    342                 '[::1]',
    343                 '::',
    344             ),
    345             // IPv6, no port, compatibility mode
    346             array(
    347                 '::ffff:10.15.20.25',
    348                 '::ffff:10.15.20.0',
    349             ),
    350             // IPv6, port, compatibility mode
    351             array(
    352                 '[::ffff:10.15.20.25]:30000',
    353                 '::ffff:10.15.20.0',
    354             ),
    355             // IPv6, no port, compatibility mode shorthand
    356             array(
    357                 '::127.0.0.1',
    358                 '::ffff:127.0.0.0',
    359             ),
    360             // IPv6, port, compatibility mode shorthand
    361             array(
    362                 '[::127.0.0.1]:30000',
    363                 '::ffff:127.0.0.0',
    364             ),
    365304        );
    366305    }
Note: See TracChangeset for help on using the changeset viewer.