WordPress.org

Make WordPress Core

Ticket #43545: 43545.5.diff

File 43545.5.diff, 17.0 KB (added by iandunn, 12 months ago)

Move $ip_prefix and tests, minor cleanup. See comment:30.

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

    diff --git src/wp-admin/includes/class-wp-community-events.php src/wp-admin/includes/class-wp-community-events.php
    index fe16dc46e4..77bf34d813 100644
    class WP_Community_Events { 
    234234         */
    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.
    240239                $address_headers = array(
    class WP_Community_Events { 
    265264                        return false;
    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, '.' ) );
     267                $anon_ip = wp_privacy_anonymize_ip( $client_ip, true );
    271268
    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 {
     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
    321276        /**
  • src/wp-includes/functions.php

    diff --git src/wp-includes/functions.php src/wp-includes/functions.php
    index 3f11cce7e0..adc88fb2fe 100644
    All at ###SITENAME### 
    61276127                ), $email_change_email['message'], $email_change_email['headers']
    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                } elseif ( ! $ipv6_fallback ) {
     6184                        return '::';
     6185                }
     6186        } elseif ( $is_ipv4 ) {
     6187                // Strip any port and partially anonymize the IP.
     6188                $last_octet_position = strrpos( $ip_addr, '.' );
     6189                $ip_addr             = substr( $ip_addr, 0, $last_octet_position ) . '.0';
     6190        } else {
     6191                return '0.0.0.0';
     6192        }
     6193
     6194        // Restore the IPv6 prefix to compatibility mode addresses.
     6195        return $ip_prefix . $ip_addr;
     6196}
     6197
     6198/**
     6199 * Return uniform "anonymous" data by type.
     6200 *
     6201 * @since 5.0.0
     6202 *
     6203 * @param  string $type The type of data to be anonymized.
     6204 * @param  string $data Optional The data to be anonymized.
     6205 * @return string The anonymous data for the requested type.
     6206 */
     6207function wp_privacy_anonymize_data( $type, $data = '' ) {
     6208
     6209        switch ( $type ) {
     6210                case 'email':
     6211                        $anonymous = 'deleted@site.invalid';
     6212                        break;
     6213                case 'url':
     6214                        $anonymous = 'https://site.invalid';
     6215                        break;
     6216                case 'ip':
     6217                        $anonymous = wp_privacy_anonymize_ip( $data );
     6218                        break;
     6219                case 'date':
     6220                        $anonymous = '0000-00-00 00:00:00';
     6221                        break;
     6222                case 'text':
     6223                        /* translators: deleted text */
     6224                        $anonymous = __( '[deleted]' );
     6225                        break;
     6226                case 'longtext':
     6227                        /* translators: deleted long text */
     6228                        $anonymous = __( 'This content was deleted by the author.' );
     6229                        break;
     6230                default:
     6231                        $anonymous = '';
     6232        }
     6233
     6234        /**
     6235         * Filters the anonymous data for each type.
     6236         *
     6237         * @since 5.0.0
     6238         *
     6239         * @param string $anonymous Anonymized data.
     6240         * @param string $type      Type of the data.
     6241         * @param string $data      Original data.
     6242         */
     6243        return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data );
     6244}
  • tests/phpunit/tests/admin/includesCommunityEvents.php

    diff --git tests/phpunit/tests/admin/includesCommunityEvents.php tests/phpunit/tests/admin/includesCommunityEvents.php
    index bd6a6cdbe6..0de7f0ab06 100644
    class Test_WP_Community_Events extends WP_UnitTestCase { 
    479479        /**
    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        /**
     495         * Provide test cases for `test_get_unsafe_client_ip()`.
     496         *
     497         * @return array
     498         */
     499        public function data_get_unsafe_client_ip() {
    494500                return array(
    495                         // Invalid IP.
    496                         array(
    497                                 '',    // Raw IP address
    498                                 false, // Expected result
    499                         ),
    500                         // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
    501                         array(
    502                                 'unknown',
    503                                 false,
    504                         ),
    505                         // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     501                        // Handle '::' returned from `wp_privacy_anonymize_ip()`.
    506502                        array(
    507503                                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001',
    508504                                false,
    509505                        ),
    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.
     506
     507                        // Handle '0.0.0.0' returned from `wp_privacy_anonymize_ip()`.
    516508                        array(
    517                                 '1000:0000:0000:0000:0000:0000:0000:0001or=\"',
     509                                'unknown',
    518510                                false,
    519511                        ),
    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
     512
     513                        // Valid IPv4.
    546514                        array(
    547                                 '10.20.30.45:20000',
    548                                 '10.20.30.0',
     515                                '198.143.164.252',
     516                                '198.143.164.0',
    549517                        ),
    550                         // IPv6, no port
     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        }
    632526}
  • new file tests/phpunit/tests/functions/anonymization.php

    diff --git tests/phpunit/tests/functions/anonymization.php tests/phpunit/tests/functions/anonymization.php
    new file mode 100644
    index 0000000000..08b5fedb63
    - +  
     1<?php
     2
     3/**
     4 * @group functions.php
     5 * @group privacy
     6 */
     7class Tests_Functions_Anonymization extends WP_UnitTestCase {
     8        /**
     9         * Test that get_unsafe_client_ip() properly anonymizes all possible address formats.
     10         *
     11         * @dataProvider data_wp_privacy_anonymize_ip
     12         *
     13         * @ticket 41083
     14         * @ticket 43545
     15         */
     16        public function test_wp_privacy_anonymize_ip( $raw_ip, $expected_result ) {
     17                $actual_result = wp_privacy_anonymize_data( 'ip', $raw_ip );
     18
     19                // todo test ipv6_fallback mode if keeping it
     20
     21                $this->assertEquals( $expected_result, $actual_result );
     22        }
     23
     24        /**
     25         * Provide test cases for `test_wp_privacy_anonymize_ip()`.
     26         *
     27         * @since 5.0.0 Moved from `Test_WP_Community_Events::data_get_unsafe_client_ip_anonymization()`.
     28         *
     29         * @return array
     30         */
     31        public function data_wp_privacy_anonymize_ip() {
     32                return array(
     33                        // Invalid IP.
     34                        array(
     35                                '',
     36                                '0.0.0.0',
     37                        ),
     38                        array(
     39                                '0.0.0.0.0',
     40                                '0.0.0.0',
     41                        ),
     42                        // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     43                        array(
     44                                'unknown',
     45                                '0.0.0.0',
     46                        ),
     47                        // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     48                        array(
     49                                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001',
     50                                '::',
     51                        ),
     52                        // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     53                        array(
     54                                'or=\"1000:0000:0000:0000:0000:0000:0000:0001',
     55                                '::',
     56                        ),
     57                        // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings.
     58                        array(
     59                                '1000:0000:0000:0000:0000:0000:0000:0001or=\"',
     60                                '::',
     61                        ),
     62                        // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
     63                        array(
     64                                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]:400',
     65                                '1000::',
     66                        ),
     67                        // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
     68                        array(
     69                                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]',
     70                                '1000::',
     71                        ),
     72                        // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
     73                        array(
     74                                'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]400',
     75                                '1000::',
     76                        ),
     77                        // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings.
     78                        array(
     79                                '[1000:0000:0000:0000:0000:0000:0000:0001]:235\"or=',
     80                                '1000::',
     81                        ),
     82                        // IPv4, no port
     83                        array(
     84                                '10.20.30.45',
     85                                '10.20.30.0',
     86                        ),
     87                        // IPv4, port
     88                        array(
     89                                '10.20.30.45:20000',
     90                                '10.20.30.0',
     91                        ),
     92                        // IPv6, no port
     93                        array(
     94                                '2a03:2880:2110:df07:face:b00c::1',
     95                                '2a03:2880:2110:df07::',
     96                        ),
     97                        // IPv6, port
     98                        array(
     99                                '[2a03:2880:2110:df07:face:b00c::1]:20000',
     100                                '2a03:2880:2110:df07::',
     101                        ),
     102                        // IPv6, no port, reducible representation
     103                        array(
     104                                '0000:0000:0000:0000:0000:0000:0000:0001',
     105                                '::',
     106                        ),
     107                        // IPv6, no port, partially reducible representation
     108                        array(
     109                                '1000:0000:0000:0000:0000:0000:0000:0001',
     110                                '1000::',
     111                        ),
     112                        // IPv6, port, reducible representation
     113                        array(
     114                                '[0000:0000:0000:0000:0000:0000:0000:0001]:1234',
     115                                '::',
     116                        ),
     117                        // IPv6, port, partially reducible representation
     118                        array(
     119                                '[1000:0000:0000:0000:0000:0000:0000:0001]:5678',
     120                                '1000::',
     121                        ),
     122                        // IPv6, no port, reduced representation
     123                        array(
     124                                '::',
     125                                '::',
     126                        ),
     127                        // IPv6, no port, reduced representation
     128                        array(
     129                                '::1',
     130                                '::',
     131                        ),
     132                        // IPv6, port, reduced representation
     133                        array(
     134                                '[::]:20000',
     135                                '::',
     136                        ),
     137                        // IPv6, address brackets without port delimiter and number, reduced representation
     138                        array(
     139                                '[::1]',
     140                                '::',
     141                        ),
     142                        // IPv6, no port, compatibility mode
     143                        array(
     144                                '::ffff:10.15.20.25',
     145                                '::ffff:10.15.20.0',
     146                        ),
     147                        // IPv6, port, compatibility mode
     148                        array(
     149                                '[::FFFF:10.15.20.25]:30000',
     150                                '::ffff:10.15.20.0',
     151                        ),
     152                        // IPv6, no port, compatibility mode shorthand
     153                        array(
     154                                '::127.0.0.1',
     155                                '::ffff:127.0.0.0',
     156                        ),
     157                        // IPv6, port, compatibility mode shorthand
     158                        array(
     159                                '[::127.0.0.1]:30000',
     160                                '::ffff:127.0.0.0',
     161                        ),
     162                        // IPv6 with reachability scope
     163                        array(
     164                                'fe80::b059:65f4:e877:c40%16',
     165                                'fe80::',
     166                        ),
     167                        // IPv6 with reachability scope
     168                        array(
     169                                'FE80::B059:65F4:E877:C40%eth0',
     170                                'fe80::',
     171                        ),
     172                );
     173        }
     174
     175        function test_anonymize_email() {
     176                $this->assertEquals( 'deleted@site.invalid', wp_privacy_anonymize_data( 'email', 'bar@example.com' ) );
     177        }
     178
     179        function test_anonymize_url() {
     180                $this->assertEquals( 'https://site.invalid', wp_privacy_anonymize_data( 'url', 'https://example.com/author/username' ) );
     181        }
     182
     183        function test_anonymize_date() {
     184                $this->assertEquals( '0000-00-00 00:00:00', wp_privacy_anonymize_data( 'date', '2003-12-25 12:34:56' ) );
     185        }
     186
     187        function test_anonymize_text() {
     188                $text = __( 'Four score and seven years ago' );
     189                $this->assertNotEquals( $text, wp_privacy_anonymize_data( 'text', $text ) );
     190        }
     191
     192        function test_anonymize_long_text() {
     193                $text = __( 'Four score and seven years ago' );
     194                $this->assertNotEquals( $text, wp_privacy_anonymize_data( 'longtext', $text ) );
     195        }
     196}