Make WordPress Core

Changeset 42971


Ignore:
Timestamp:
04/12/2018 09:19:24 PM (8 years 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.