← All changes
|
jetpack_vendor/automattic/jetpack-status/src/class-visitor.php
+85
-5
12.5.2
→
16.3
View file →
| @@ -6,8 +6,10 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | namespace Automattic\Jetpack\Status; |
| 9 | 9 | |
| 10 | +use Automattic\Jetpack\IP\Utils as IP_Utils; | |
| 11 | + | |
| 10 | 12 | /** |
| 11 | 13 | * Visitor class. |
| 12 | 14 | */ |
| 13 | 15 | class Visitor { |
| @@ -14,14 +16,42 @@ | ||
| 14 | 16 | |
| 15 | 17 | /** |
| 16 | 18 | * Gets current user IP address. |
| 17 | 19 | * |
| 20 | + * Only a value that parses as an IP address is returned. With `$check_all_headers`, the | |
| 21 | + * forwarded headers are tried in order, a comma-separated list yields its first valid entry, | |
| 22 | + * and a header holding no valid address is skipped. | |
| 23 | + * | |
| 24 | + * A site with a trusted header configured does not use that sweep at all. Brute force | |
| 25 | + * protection stores which header carries the visitor address, how far back to count in its | |
| 26 | + * list, and whether that list runs in reverse, all worked out for this site's own proxy | |
| 27 | + * setup. Once that answer exists it is the answer, including when the request did not carry | |
| 28 | + * the header — `IP\Utils::get_ip()` falls back to `REMOTE_ADDR` itself in that case. Reading | |
| 29 | + * the sweep afterwards would let a request that simply omits the trusted header pick its own | |
| 30 | + * address out of a header it fully controls, and would hand Jetpack two answers for one | |
| 31 | + * request, since brute force protection resolves the same visitor through `IP\Utils`. | |
| 32 | + * | |
| 33 | + * That is a guarantee about which source is consulted, not about the address itself: it | |
| 34 | + * still assumes the proxy named in the stored configuration rewrites the header. A request | |
| 35 | + * reaching the origin directly can present that header itself. | |
| 36 | + * | |
| 37 | + * The address is normalized by `IP\Utils::clean_ip()`: it is lowercased, anything following an | |
| 38 | + * " unless " separator is dropped, and a port suffix, IPv6 brackets, or an `::ffff:` IPv4 | |
| 39 | + * mapping are reduced to the bare address. Code comparing this value against a stored or | |
| 40 | + * configured address should normalize that address the same way. | |
| 41 | + * | |
| 18 | 42 | * @param bool $check_all_headers Check all headers? Default is `false`. |
| 19 | 43 | * |
| 20 | - * @return string Current user IP address. | |
| 44 | + * @return string Current user IP address, or an empty string if no valid address could be determined. | |
| 21 | 45 | */ |
| 22 | 46 | public function get_ip( $check_all_headers = false ) { |
| 23 | 47 | if ( $check_all_headers ) { |
| 48 | + $trusted_header_data = get_site_option( 'trusted_ip_header' ); | |
| 49 | + if ( isset( $trusted_header_data->trusted_header ) ) { | |
| 50 | + $trusted_ip = IP_Utils::get_ip(); | |
| 51 | + return false !== $trusted_ip ? $trusted_ip : ''; | |
| 52 | + } | |
| 53 | + | |
| 24 | 54 | foreach ( array( |
| 25 | 55 | 'HTTP_CF_CONNECTING_IP', |
| 26 | 56 | 'HTTP_CLIENT_IP', |
| 27 | 57 | 'HTTP_X_FORWARDED_FOR', |
| @@ -30,15 +60,65 @@ | ||
| 30 | 60 | 'HTTP_FORWARDED_FOR', |
| 31 | 61 | 'HTTP_FORWARDED', |
| 32 | 62 | 'HTTP_VIA', |
| 33 | 63 | ) as $key ) { |
| 34 | - if ( ! empty( $_SERVER[ $key ] ) ) { | |
| 35 | - // @todo Some of these might actually be lists of IPs (e.g. HTTP_X_FORWARDED_FOR) or something else entirely (HTTP_VIA). | |
| 36 | - return filter_var( wp_unslash( $_SERVER[ $key ] ) ); | |
| 64 | + if ( empty( $_SERVER[ $key ] ) ) { | |
| 65 | + continue; | |
| 37 | 66 | } |
| 67 | + // Proxies append to the list, so the leftmost entry is the client. | |
| 68 | + foreach ( explode( ',', (string) wp_unslash( $_SERVER[ $key ] ) ) as $candidate ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each entry is validated by clean_ip() below. | |
| 69 | + $ip = IP_Utils::clean_ip( $candidate ); | |
| 70 | + if ( false !== $ip ) { | |
| 71 | + return $ip; | |
| 72 | + } | |
| 73 | + } | |
| 38 | 74 | } |
| 39 | 75 | } |
| 40 | 76 | |
| 41 | - return ! empty( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; | |
| 77 | + $ip = empty( $_SERVER['REMOTE_ADDR'] ) ? false : IP_Utils::clean_ip( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- clean_ip() validates it. | |
| 78 | + return false !== $ip ? $ip : ''; | |
| 42 | 79 | } |
| 43 | 80 | |
| 81 | + /** | |
| 82 | + * Simple gate check for a11n feature testing purposes using AT_PROXIED_REQUEST constant. | |
| 83 | + * IMPORTANT: Only use it for internal feature test purposes, not authorization. | |
| 84 | + * | |
| 85 | + * The goal of this function is to help us gate features by using a similar function name | |
| 86 | + * we find on simple sites: is_automattician(). | |
| 87 | + * | |
| 88 | + * @return bool True if the current request is PROXIED, false otherwise. | |
| 89 | + */ | |
| 90 | + public function is_automattician_feature_flags_only() { | |
| 91 | + return ( defined( 'AT_PROXIED_REQUEST' ) && AT_PROXIED_REQUEST ); | |
| 92 | + } | |
| 93 | + | |
| 94 | + /** | |
| 95 | + * Whether the current request should be attributed to an Automattician in analytics. | |
| 96 | + * | |
| 97 | + * True for an identified Automattician on WordPress.com Simple, and for A8C-proxied | |
| 98 | + * requests on both Simple and WoA. Use it to tag Tracks events as internal traffic so | |
| 99 | + * it can be filtered out of product reporting — this matters most for newly launched | |
| 100 | + * features, where a small amount of internal testing is a large share of the totals | |
| 101 | + * and there is no way to separate it after the fact. | |
| 102 | + * | |
| 103 | + * IMPORTANT: Reporting signal only, never authorization. A proxied request says | |
| 104 | + * something about where the request came from, not who the user is. | |
| 105 | + * | |
| 106 | + * @since 6.4.0 | |
| 107 | + * | |
| 108 | + * @return bool True if the request looks like Automattician traffic, false otherwise. | |
| 109 | + */ | |
| 110 | + public function is_tracking_automattician() { | |
| 111 | + // Identified Automattician on WordPress.com Simple. | |
| 112 | + if ( function_exists( 'is_automattician' ) && \is_automattician() ) { | |
| 113 | + return true; | |
| 114 | + } | |
| 115 | + | |
| 116 | + // Proxied A8C request on WordPress.com Simple. | |
| 117 | + if ( function_exists( 'wpcom_is_proxied_request' ) && \wpcom_is_proxied_request() ) { | |
| 118 | + return true; | |
| 119 | + } | |
| 120 | + | |
| 121 | + // Proxied A8C request on WoA. | |
| 122 | + return $this->is_automattician_feature_flags_only(); | |
| 123 | + } | |
| 44 | 124 | } |