Make WordPress Core

Changeset 43082


Ignore:
Timestamp:
05/02/2018 12:48:23 AM (8 years 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.