Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Brute_Force_Protection_Shared_Functions
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 5
1482
0.00% covered (danger)
0.00%
0 / 1
 format_whitelist
n/a
0 / 0
n/a
0 / 0
1
 format_allow_list
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
72
 get_local_whitelist
n/a
0 / 0
n/a
0 / 0
1
 get_local_allow_list
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 get_global_whitelist
n/a
0 / 0
n/a
0 / 0
1
 get_global_allow_list
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 create_ip_object
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 save_whitelist
n/a
0 / 0
n/a
0 / 0
1
 save_allow_list
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
342
1<?php
2/**
3 * Class for functions shared by the Brute force protection feature and its related json-endpoints
4 *
5 * @package automattic/jetpack-waf
6 */
7
8namespace Automattic\Jetpack\Waf\Brute_Force_Protection;
9
10use Automattic\Jetpack\IP\Utils as IP_Utils;
11use Automattic\Jetpack\Waf\Waf_Rules_Manager;
12use WP_Error;
13
14/**
15 * Shared Functions class.
16 */
17class Brute_Force_Protection_Shared_Functions {
18    /**
19     * Returns an array of IP objects that will never be blocked by the Brute force protection feature
20     *
21     * @deprecated 0.11.0 Use format_allow_list()
22     */
23    public static function format_whitelist() {
24        _deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::format_allow_list' );
25        return self::format_allow_list();
26    }
27
28    /**
29     * Returns an array of IP objects that will never be blocked by the Brute force protection feature
30     *
31     * The array is segmented into a local allow list which applies only to the current site
32     * and a global allow list which, for multisite installs, applies to the entire networko
33     *
34     * @return array
35     */
36    public static function format_allow_list() {
37        $local_allow_list = self::get_local_allow_list();
38        $formatted        = array(
39            'local' => array(),
40        );
41        foreach ( $local_allow_list as $item ) {
42            if ( $item->range ) {
43                $formatted['local'][] = $item->range_low . ' - ' . $item->range_high;
44            } else {
45                $formatted['local'][] = $item->ip_address;
46            }
47        }
48        if ( is_multisite() && current_user_can( 'manage_network' ) ) {
49            $formatted['global'] = array();
50            $global_allow_list   = self::get_global_allow_list();
51            if ( false === $global_allow_list ) {
52                // If the global allow list has never been set, check for a legacy option set prior to 3.6.
53                $global_allow_list = get_site_option( 'jetpack_protect_whitelist', array() );
54            }
55            foreach ( $global_allow_list as $item ) {
56                if ( $item->range ) {
57                    $formatted['global'][] = $item->range_low . ' - ' . $item->range_high;
58                } else {
59                    $formatted['global'][] = $item->ip_address;
60                }
61            }
62        }
63        return $formatted;
64    }
65
66    /**
67     * Gets the local Brute force protection allow list.
68     *
69     * @deprecated 0.11.0 Use get_local_allow_list()
70     */
71    public static function get_local_whitelist() {
72        _deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::get_local_allow_list' );
73        return self::get_local_allow_list();
74    }
75
76    /**
77     * Gets the local Brute force protection allow list.
78     *
79     * The 'local' part of the allow list only really applies to multisite installs,
80     * which can have a network wide allow list, as well as a local list that applies
81     * only to the current site. On single site installs, there will only be a local
82     * allow list.
83     *
84     * @return array A list of IP Address objects or an empty array
85     */
86    public static function get_local_allow_list() {
87        $allow_list = get_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME );
88        if ( false === $allow_list ) {
89            // The local allow list has never been set.
90            if ( is_multisite() ) {
91                // On a multisite, we can check for a legacy site_option that existed prior to v 3.6, or default to an empty array.
92                $allow_list = get_site_option( 'jetpack_protect_whitelist', array() );
93            } else {
94                // On a single site, we can just use an empty array.
95                $allow_list = array();
96            }
97        } else {
98            $allow_list = IP_Utils::get_ip_addresses_from_string( $allow_list );
99            $allow_list = array_map(
100                function ( $ip_address ) {
101                    return self::create_ip_object( $ip_address );
102                },
103                $allow_list
104            );
105        }
106        return $allow_list;
107    }
108
109    /**
110     * Get the global, network-wide allow list.
111     *
112     * @deprecated 0.11.0 Use get_global_allow_list()
113     */
114    public static function get_global_whitelist() {
115        _deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::get_global_allow_list' );
116        return self::get_global_allow_list();
117    }
118
119    /**
120     * Get the global, network-wide allow list.
121     *
122     * It will revert to the legacy site_option if jetpack_protect_global_whitelist has never been set.
123     *
124     * @return array
125     */
126    public static function get_global_allow_list() {
127        $allow_list = get_site_option( 'jetpack_protect_global_whitelist' );
128        if ( false === $allow_list ) {
129            // The global allow list has never been set. Check for legacy site_option, or default to an empty array.
130            $allow_list = get_site_option( 'jetpack_protect_whitelist', array() );
131        }
132        return $allow_list;
133    }
134
135    /**
136     * Convert a string into an IP Address object.
137     *
138     * @param string $ip_address The IP Address to convert.
139     * @return object An IP Address object.
140     */
141    private static function create_ip_object( $ip_address ) {
142        // Hyphenated range notation.
143        if ( strpos( $ip_address, '-' ) ) {
144            $ip_range_parts = explode( '-', $ip_address );
145            return (object) array(
146                'range'      => true,
147                'range_low'  => trim( $ip_range_parts[0] ),
148                'range_high' => trim( $ip_range_parts[1] ),
149            );
150        }
151
152        // CIDR notation.
153        if ( strpos( $ip_address, '/' ) !== false ) {
154            return (object) array(
155                'range'      => true,
156                'range_low'  => $ip_address,
157                'range_high' => null,
158            );
159        }
160
161        // Single IP Address.
162        return (object) array(
163            'range'      => false,
164            'ip_address' => $ip_address,
165        );
166    }
167
168    /**
169     * Save IP allow list.
170     *
171     * @deprecated 0.11.0 Use save_allow_list()
172     *
173     * @param mixed $allow_list IP allow list.
174     * @param bool  $global (default: false) Global.
175     */
176    public static function save_whitelist( $allow_list, $global = false ) {
177        _deprecated_function( __METHOD__, 'waf-0.11.0', __CLASS__ . '::save_allow_list' );
178        return self::save_allow_list( $allow_list, $global );
179    }
180
181    /**
182     * Save IP allow list.
183     *
184     * @access public
185     * @param mixed $allow_list IP allow list.
186     * @param bool  $global (default: false) Global.
187     * @return bool
188     */
189    public static function save_allow_list( $allow_list, $global = false ) {
190        $allow_list_error = false;
191        $new_items        = array();
192        if ( ! is_array( $allow_list ) ) {
193            return new WP_Error( 'invalid_parameters', __( 'Expecting an array', 'jetpack-waf' ) );
194        }
195        if ( $global && ! is_multisite() ) {
196            return new WP_Error( 'invalid_parameters', __( 'Cannot use global flag on non-multisites', 'jetpack-waf' ) );
197        }
198        if ( $global && ! current_user_can( 'manage_network' ) ) {
199            return new WP_Error( 'permission_denied', __( 'Only super admins can edit the global allow list', 'jetpack-waf' ) );
200        }
201        // Validate each item.
202        foreach ( $allow_list as $item ) {
203            $item = trim( $item );
204            if ( empty( $item ) ) {
205                continue;
206            }
207            $new_item = self::create_ip_object( $item );
208            if ( $new_item->range ) {
209                if ( ! filter_var( $new_item->range_low, FILTER_VALIDATE_IP ) || ! filter_var( $new_item->range_high, FILTER_VALIDATE_IP ) ) {
210                    $allow_list_error = true;
211                    break;
212                }
213                if ( ! IP_Utils::convert_ip_address( $new_item->range_low ) || ! IP_Utils::convert_ip_address( $new_item->range_high ) ) {
214                    $allow_list_error = true;
215                    break;
216                }
217            } else {
218                if ( ! filter_var( $new_item->ip_address, FILTER_VALIDATE_IP ) ) {
219                    $allow_list_error = true;
220                    break;
221                }
222                if ( ! IP_Utils::convert_ip_address( $new_item->ip_address ) ) {
223                    $allow_list_error = true;
224                    break;
225                }
226            }
227            $new_items[] = $new_item;
228        } // End item loop.
229        if ( ! empty( $allow_list_error ) ) {
230            return new WP_Error( 'invalid_ip', __( 'One of your IP addresses was not valid.', 'jetpack-waf' ) );
231        }
232        if ( $global ) {
233            update_site_option( 'jetpack_protect_global_whitelist', $new_items );
234            // Once a user has saved their global allow list, we can permanently remove the legacy option.
235            delete_site_option( 'jetpack_protect_whitelist' );
236        } else {
237            $new_items = array_map(
238                function ( $item ) {
239                    if ( $item->range ) {
240                            return $item->range_low . '-' . $item->range_high;
241                    }
242                    return $item->ip_address;
243                },
244                $new_items
245            );
246            update_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME, implode( ' ', $new_items ) );
247        }
248        return true;
249    }
250}