WordPress.org

Make WordPress Core

Changeset 42971


Ignore:
Timestamp:
04/12/2018 09:19:24 PM (12 months ago)
Author:
azaozz
Message:

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

Props jesperher, allendav, iandunn, birgire, azaozz.
Fixes #43545.

Location:
trunk
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/class-wp-community-events.php

    r42968 r42971  
    235235    public static function get_unsafe_client_ip() {
    236236        $client_ip = 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             $left_bracket  = strpos( $client_ip, '[' );
    283             $right_bracket = strpos( $client_ip, ']' );
    284             $percent       = strpos( $client_ip, '%' );
    285             $netmask       = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
    286 
    287             // Strip the port (and [] from IPv6 addresses), if they exist.
    288             if ( false !== $left_bracket && false !== $right_bracket ) {
    289                 $client_ip = substr( $client_ip, $left_bracket + 1, $right_bracket - $left_bracket - 1 );
    290             } elseif ( false !== $left_bracket || false !== $right_bracket ) {
    291                 // The IP has one bracket, but not both, so it's malformed.
    292                 return false;
    293             }
    294 
    295             // Strip the reachability scope.
    296             if ( false !== $percent ) {
    297                 $client_ip = substr( $client_ip, 0, $percent );
    298             }
    299 
    300             // No invalid characters should be left.
    301             if ( preg_match( '/[^0-9a-f:]/i', $client_ip ) ) {
    302                 return false;
    303             }
    304 
    305             // Partially anonymize the IP by reducing it to the corresponding network ID.
    306             if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
    307                 $client_ip = inet_ntop( inet_pton( $client_ip ) & inet_pton( $netmask ) );
    308             }
    309         } elseif ( $is_ipv4 ) {
    310             // Strip any port and partially anonymize the IP.
    311             $last_octet_position = strrpos( $client_ip, '.' );
    312             $client_ip           = substr( $client_ip, 0, $last_octet_position ) . '.0';
    313         } else {
     267        $anon_ip = wp_privacy_anonymize_ip( $client_ip, true );
     268
     269        if ( '0.0.0.0' === $anon_ip || '::' === $anon_ip ) {
    314270            return false;
    315271        }
    316272
    317         // Restore the IPv6 prefix to compatibility mode addresses.
    318         return $ip_prefix . $client_ip;
     273        return $anon_ip;
    319274    }
    320275
  • trunk/src/wp-includes/functions.php

    r42876 r42971  
    61286128    );
    61296129}
     6130
     6131/**
     6132 * Return an anonymized IPv4 or IPv6 address.
     6133 *
     6134 * @since 5.0.0 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`.
     6135 *
     6136 * @param  string $ip_addr        The IPv4 or IPv6 address to be anonymized.
     6137 * @param  bool   $ipv6_fallback  Optional. Whether to return the original IPv6 address if the needed functions
     6138 *                                to anonymize it are not present. Default false, return `::` (unspecified address).
     6139 * @return string  The anonymized IP address.
     6140 */
     6141function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) {
     6142    // Detect what kind of IP address this is.
     6143    $ip_prefix = '';
     6144    $is_ipv6   = substr_count( $ip_addr, ':' ) > 1;
     6145    $is_ipv4   = ( 3 === substr_count( $ip_addr, '.' ) );
     6146
     6147    if ( $is_ipv6 && $is_ipv4 ) {
     6148        // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4.
     6149        $ip_prefix = '::ffff:';
     6150        $ip_addr   = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr );
     6151        $ip_addr   = str_replace( ']', '', $ip_addr );
     6152        $is_ipv6   = false;
     6153    }
     6154
     6155    if ( $is_ipv6 ) {
     6156        // IPv6 addresses will always be enclosed in [] if there's a port.
     6157        $left_bracket  = strpos( $ip_addr, '[' );
     6158        $right_bracket = strpos( $ip_addr, ']' );
     6159        $percent       = strpos( $ip_addr, '%' );
     6160        $netmask       = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000';
     6161
     6162        // Strip the port (and [] from IPv6 addresses), if they exist.
     6163        if ( false !== $left_bracket && false !== $right_bracket ) {
     6164            $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 );
     6165        } elseif ( false !== $left_bracket || false !== $right_bracket ) {
     6166            // The IP has one bracket, but not both, so it's malformed.
     6167            return '::';
     6168        }
     6169
     6170        // Strip the reachability scope.
     6171        if ( false !== $percent ) {
     6172            $ip_addr = substr( $ip_addr, 0, $percent );
     6173        }
     6174
     6175        // No invalid characters should be left.
     6176        if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) {
     6177            return '::';
     6178        }
     6179
     6180        // Partially anonymize the IP by reducing it to the corresponding network ID.
     6181        if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) {
     6182            $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) );
     6183            if ( false === $ip_addr) {
     6184                return '::';
     6185            }
     6186        } elseif ( ! $ipv6_fallback ) {
     6187            return '::';
     6188        }
     6189    } elseif ( $is_ipv4 ) {
     6190        // Strip any port and partially anonymize the IP.
     6191        $last_octet_position = strrpos( $ip_addr, '.' );
     6192        $ip_addr             = substr( $ip_addr, 0, $last_octet_position ) . '.0';
     6193    } else {
     6194        return '0.0.0.0';
     6195    }
     6196
     6197    // Restore the IPv6 prefix to compatibility mode addresses.
     6198    return $ip_prefix . $ip_addr;
     6199}
     6200
     6201/**
     6202 * Return uniform "anonymous" data by type.
     6203 *
     6204 * @since 5.0.0
     6205 *
     6206 * @param  string $type The type of data to be anonymized.
     6207 * @param  string $data Optional The data to be anonymized.
     6208 * @return string The anonymous data for the requested type.
     6209 */
     6210function wp_privacy_anonymize_data( $type, $data = '' ) {
     6211
     6212    switch ( $type ) {
     6213        case 'email':
     6214            $anonymous = 'deleted@site.invalid';
     6215            break;
     6216        case 'url':
     6217            $anonymous = 'https://site.invalid';
     6218            break;
     6219        case 'ip':
     6220            $anonymous = wp_privacy_anonymize_ip( $data );
     6221            break;
     6222        case 'date':
     6223            $anonymous = '0000-00-00 00:00:00';
     6224            break;
     6225        case 'text':
     6226            /* translators: deleted text */
     6227            $anonymous = __( '[deleted]' );
     6228            break;
     6229        case 'longtext':
     6230            /* translators: deleted long text */
     6231            $anonymous = __( 'This content was deleted by the author.' );
     6232            break;
     6233        default:
     6234            $anonymous = '';
     6235    }
     6236
     6237    /**
     6238     * Filters the anonymous data for each type.
     6239     *
     6240     * @since 5.0.0
     6241     *
     6242     * @param string $anonymous Anonymized data.
     6243     * @param string $type      Type of the data.
     6244     * @param string $data      Original data.
     6245     */
     6246    return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
     6247}
  • trunk/tests/phpunit/tests/admin/includesCommunityEvents.php

    r42968 r42971  
    480480     * Test that get_unsafe_client_ip() properly anonymizes all possible address formats
    481481     *
    482      * @dataProvider data_get_unsafe_client_ip_anonymization
     482     * @dataProvider data_get_unsafe_client_ip
    483483     *
    484484     * @ticket 41083
    485485     */
    486     public function test_get_unsafe_client_ip_anonymization( $raw_ip, $expected_result ) {
    487         $_SERVER['REMOTE_ADDR'] = $raw_ip;
    488         $actual_result          = WP_Community_Events::get_unsafe_client_ip();
     486    public function test_get_unsafe_client_ip( $raw_ip, $expected_result ) {
     487        $_SERVER['REMOTE_ADDR']    = 'this should not be used';
     488        $_SERVER['HTTP_CLIENT_IP'] = $raw_ip;
     489        $actual_result             = WP_Community_Events::get_unsafe_client_ip();
    489490
    490491        $this->assertEquals( $expected_result, $actual_result );
    491492    }
    492493
    493     public function data_get_unsafe_client_ip_anonymization() {
    494         return array(
    495             // Invalid IP.
     494    /**
     495     * Provide test cases for `test_get_unsafe_client_ip()`.
     496     *
     497     * @return array
     498     */
     499    public function data_get_unsafe_client_ip() {
     500        return array(
     501            // Handle '::' returned from `wp_privacy_anonymize_ip()`.
    496502            array(
    497                 '',    // Raw IP address
    498                 false, // Expected result
    499             ),
    500             // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     503                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001',
     504                false,
     505            ),
     506
     507            // Handle '0.0.0.0' returned from `wp_privacy_anonymize_ip()`.
    501508            array(
    502509                'unknown',
    503510                false,
    504511            ),
    505             // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     512
     513            // Valid IPv4.
    506514            array(
    507                 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001',
    508                 false,
    509             ),
    510             // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
    511             array(
    512                 'or=\"1000:0000:0000:0000:0000:0000:0000:0001',
    513                 false,
    514             ),
    515             // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
    516             array(
    517                 '1000:0000:0000:0000:0000:0000:0000:0001or=\"',
    518                 false,
    519             ),
    520             // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
    521             array(
    522                 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]:400',
    523                 '1000::',
    524             ),
    525             // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
    526             array(
    527                 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]',
    528                 '1000::',
    529             ),
    530             // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
    531             array(
    532                 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]400',
    533                 '1000::',
    534             ),
    535             // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
    536             array(
    537                 '[1000:0000:0000:0000:0000:0000:0000:0001]:235\"or=',
    538                 '1000::',
    539             ),
    540             // IPv4, no port
    541             array(
    542                 '10.20.30.45',
    543                 '10.20.30.0',
    544             ),
    545             // IPv4, port
    546             array(
    547                 '10.20.30.45:20000',
    548                 '10.20.30.0',
    549             ),
    550             // IPv6, no port
     515                '198.143.164.252',
     516                '198.143.164.0',
     517            ),
     518
     519            // Valid IPv6.
    551520            array(
    552521                '2a03:2880:2110:df07:face:b00c::1',
    553522                '2a03:2880:2110:df07::',
    554523            ),
    555             // IPv6, port
    556             array(
    557                 '[2a03:2880:2110:df07:face:b00c::1]:20000',
    558                 '2a03:2880:2110:df07::',
    559             ),
    560             // IPv6, no port, reducible representation
    561             array(
    562                 '0000:0000:0000:0000:0000:0000:0000:0001',
    563                 '::',
    564             ),
    565             // IPv6, no port, partially reducible representation
    566             array(
    567                 '1000:0000:0000:0000:0000:0000:0000:0001',
    568                 '1000::',
    569             ),
    570             // IPv6, port, reducible representation
    571             array(
    572                 '[0000:0000:0000:0000:0000:0000:0000:0001]:1234',
    573                 '::',
    574             ),
    575             // IPv6, port, partially reducible representation
    576             array(
    577                 '[1000:0000:0000:0000:0000:0000:0000:0001]:5678',
    578                 '1000::',
    579             ),
    580             // IPv6, no port, reduced representation
    581             array(
    582                 '::',
    583                 '::',
    584             ),
    585             // IPv6, no port, reduced representation
    586             array(
    587                 '::1',
    588                 '::',
    589             ),
    590             // IPv6, port, reduced representation
    591             array(
    592                 '[::]:20000',
    593                 '::',
    594             ),
    595             // IPv6, address brackets without port delimiter and number, reduced representation
    596             array(
    597                 '[::1]',
    598                 '::',
    599             ),
    600             // IPv6, no port, compatibility mode
    601             array(
    602                 '::ffff:10.15.20.25',
    603                 '::ffff:10.15.20.0',
    604             ),
    605             // IPv6, port, compatibility mode
    606             array(
    607                 '[::FFFF:10.15.20.25]:30000',
    608                 '::ffff:10.15.20.0',
    609             ),
    610             // IPv6, no port, compatibility mode shorthand
    611             array(
    612                 '::127.0.0.1',
    613                 '::ffff:127.0.0.0',
    614             ),
    615             // IPv6, port, compatibility mode shorthand
    616             array(
    617                 '[::127.0.0.1]:30000',
    618                 '::ffff:127.0.0.0',
    619             ),
    620             // IPv6 with reachability scope
    621             array(
    622                 'fe80::b059:65f4:e877:c40%16',
    623                 'fe80::',
    624             ),
    625             // IPv6 with reachability scope
    626             array(
    627                 'FE80::B059:65F4:E877:C40%eth0',
    628                 'fe80::',
    629             ),
    630524        );
    631525    }
Note: See TracChangeset for help on using the changeset viewer.