Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
46 / 69
28.57% covered (danger)
28.57%
4 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Waf_Compatibility
66.67% covered (warning)
66.67%
46 / 69
28.57% covered (danger)
28.57%
4 / 14
73.33
0.00% covered (danger)
0.00%
0 / 1
 get_ip_allow_list_enabled_option_name
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 get_ip_block_list_enabled_option_name
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 add_compatibility_hooks
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 run_compatibility_migrations
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 default_option_waf_automatic_rules
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 get_default_automatic_rules_option
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 default_option_waf_needs_update
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 merge_ip_allow_lists
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 migrate_brute_force_protection_ip_allow_list
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 filter_option_waf_ip_allow_list
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 default_option_waf_ip_allow_list
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 is_brute_force_running_in_jetpack
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 default_option_waf_ip_allow_list_enabled
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 default_option_waf_ip_block_list_enabled
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2/**
3 * Class used to manage backwards-compatibility of the package.
4 *
5 * @since 0.8.0
6 *
7 * @package automattic/jetpack-waf
8 */
9
10namespace Automattic\Jetpack\Waf;
11
12use Jetpack_Options;
13
14/**
15 * Defines methods for ensuring backwards compatibility.
16 */
17class Waf_Compatibility {
18
19    /**
20     * Returns the name for the IP allow list enabled/disabled option.
21     *
22     * @since 0.22.0
23     *
24     * @return string
25     */
26    private static function get_ip_allow_list_enabled_option_name() {
27        /**
28         * Patch: bootstrap script generated prior to 0.17.0 may have autoloaded Waf_Rules_Manager class during standalone mode execution.
29         *
30         * @see peb6dq-2HL-p2
31         */
32        if ( ! defined( 'Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME' ) ) {
33            return 'jetpack_waf_ip_allow_list_enabled';
34        }
35
36        return Waf_Rules_Manager::IP_ALLOW_LIST_ENABLED_OPTION_NAME;
37    }
38
39    /**
40     * Returns the name for the IP block list enabled/disabled option.
41     *
42     * @since 0.22.0
43     *
44     * @return string
45     */
46    private static function get_ip_block_list_enabled_option_name() {
47        /**
48         * Patch: bootstrap script generated prior to 0.17.0 may have autoloaded Waf_Rules_Manager class during standalone mode execution.
49         *
50         * @see peb6dq-2HL-p2
51         */
52        if ( ! defined( 'Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME' ) ) {
53            return 'jetpack_waf_ip_block_list_enabled';
54        }
55
56        return Waf_Rules_Manager::IP_BLOCK_LIST_ENABLED_OPTION_NAME;
57    }
58
59    /**
60     * Add compatibilty hooks
61     *
62     * @since 0.8.0
63     *
64     * @return void
65     */
66    public static function add_compatibility_hooks() {
67        add_filter( 'default_option_' . Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME, __CLASS__ . '::default_option_waf_automatic_rules', 10, 3 );
68        add_filter( 'default_option_' . Waf_Initializer::NEEDS_UPDATE_OPTION_NAME, __CLASS__ . '::default_option_waf_needs_update', 10, 3 );
69        add_filter( 'default_option_' . Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME, __CLASS__ . '::default_option_waf_ip_allow_list', 10, 3 );
70        add_filter( 'option_' . Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME, __CLASS__ . '::filter_option_waf_ip_allow_list', 10, 1 );
71        add_filter( 'default_option_' . self::get_ip_allow_list_enabled_option_name(), __CLASS__ . '::default_option_waf_ip_allow_list_enabled', 10, 3 );
72        add_filter( 'default_option_' . self::get_ip_block_list_enabled_option_name(), __CLASS__ . '::default_option_waf_ip_block_list_enabled', 10, 3 );
73    }
74
75    /**
76     * Run compatibility migrations.
77     *
78     * Note that this method should be compatible with sites where
79     * the request firewall is not active or not supported.
80     *
81     * @see Waf_Runner::is_supported_environment().
82     *
83     * @since 0.11.0
84     *
85     * @return void
86     */
87    public static function run_compatibility_migrations() {
88        self::migrate_brute_force_protection_ip_allow_list();
89    }
90
91    /**
92     * Provides a default value for sites that installed the WAF
93     * before the automatic rules option was introduced.
94     *
95     * @since 0.9.0
96     *
97     * @param mixed  $default         The default value to return if the option does not exist in the database.
98     * @param string $option          Option name.
99     * @param bool   $passed_default  Was get_option() passed a default value.
100     *
101     * @return mixed The default value to return if the option does not exist in the database.
102     */
103    public static function default_option_waf_automatic_rules( $default, $option, $passed_default ) {
104        // Allow get_option() to override this default value
105        if ( $passed_default ) {
106            return $default;
107        }
108
109        return self::get_default_automatic_rules_option();
110    }
111
112    /**
113     * If the option is not available, use the WAF module status
114     * to determine whether or not to run automatic rules.
115     *
116     * @since 0.9.0
117     *
118     * @return bool The default value for automatic rules.
119     */
120    public static function get_default_automatic_rules_option() {
121        return Waf_Runner::is_enabled();
122    }
123
124    /**
125     * Provides a default value for sites that installed the WAF
126     * before the NEEDS_UPDATE_OPTION_NAME option was added.
127     *
128     * @since 0.8.0
129     *
130     * @param mixed  $default         The default value to return if the option does not exist in the database.
131     * @param string $option          Option name.
132     * @param bool   $passed_default  Was get_option() passed a default value.
133     *
134     * @return mixed The default value to return if the option does not exist in the database.
135     */
136    public static function default_option_waf_needs_update( $default, $option, $passed_default ) {
137        // Allow get_option() to override this default value
138        if ( $passed_default ) {
139            return $default;
140        }
141
142        // If the option hasn't been added yet, the WAF needs to be updated.
143        return true;
144    }
145
146    /**
147     * Merge the WAF and Brute Force Protection IP allow lists.
148     *
149     * @since 0.11.0
150     *
151     * @param string $waf_allow_list        The WAF IP allow list.
152     * @param array  $brute_force_allow_list The Brute Force Protection IP allow list. Array of IP objects.
153     *
154     * @return string The merged IP allow list.
155     */
156    public static function merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list ) {
157
158        // Drop malformed entries.
159        $brute_force_allow_list = is_array( $brute_force_allow_list ) ? array_filter( $brute_force_allow_list, 'is_object' ) : array();
160
161        if ( empty( $brute_force_allow_list ) ) {
162            return $waf_allow_list;
163        }
164
165        // Convert the IP objects to strings.
166        $brute_force_allow_list = array_map(
167            function ( $ip_object ) {
168                if ( ! empty( $ip_object->range ) ) {
169                    return $ip_object->range_low . '-' . $ip_object->range_high;
170                }
171
172                return $ip_object->ip_address;
173            },
174            $brute_force_allow_list
175        );
176
177        $brute_force_allow_list_string = implode( "\n", $brute_force_allow_list );
178
179        if ( empty( $waf_allow_list ) ) {
180            return $brute_force_allow_list_string;
181        }
182
183        // Return the lists merged into a single string.
184        return "$waf_allow_list\n$brute_force_allow_list_string";
185    }
186
187    /**
188     * Migrate the brute force protection IP allow list option to the WAF option.
189     *
190     * @since 0.11.0
191     *
192     * @return void
193     */
194    public static function migrate_brute_force_protection_ip_allow_list() {
195        // Get the allow list values directly from the database to avoid filters.
196        $brute_force_allow_list = Jetpack_Options::get_raw_option( 'jetpack_protect_whitelist' );
197        $waf_allow_list         = Jetpack_Options::get_raw_option( 'jetpack_waf_ip_allow_list' );
198
199        if ( ! empty( $brute_force_allow_list ) ) {
200
201            if ( empty( $waf_allow_list ) ) {
202                $waf_allow_list = '';
203            }
204
205            // Merge the two allow lists.
206            $merged_allow_list = self::merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list );
207
208            // Update the WAF IP allow list with the merged list.
209            Jetpack_Options::update_raw_option( 'jetpack_waf_ip_allow_list', $merged_allow_list );
210
211            // Delete the old option if the update was successful.
212            // Check the values directly as `update_raw_option()` returns false if the value hasn't changed.
213            if ( Jetpack_Options::get_raw_option( 'jetpack_waf_ip_allow_list' ) === $merged_allow_list ) {
214                delete_option( 'jetpack_protect_whitelist' );
215            }
216        }
217    }
218
219    /**
220     * Filter for Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME's option value.
221     * Merges the deprecated IP allow list from the brute force protection module
222     * with the existing option value, and flags that the WAF needs to be updated.
223     *
224     * @since 0.11.0
225     *
226     * @param array $waf_allow_list The current value of the option.
227     *
228     * @return array The merged IP allow list.
229     */
230    public static function filter_option_waf_ip_allow_list( $waf_allow_list ) {
231        $brute_force_allow_list = Jetpack_Options::get_raw_option( 'jetpack_protect_whitelist', false );
232        if ( false !== $brute_force_allow_list ) {
233            $waf_allow_list = self::merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list );
234            update_option( Waf_Initializer::NEEDS_UPDATE_OPTION_NAME, true );
235        }
236
237        return $waf_allow_list;
238    }
239
240    /**
241     * Default option for when the Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME option is not set.
242     *
243     * @param mixed  $default         The default value to return if the option does not exist in the database.
244     * @param string $option          Option name.
245     * @param bool   $passed_default  Was get_option() passed a default value.
246     *
247     * @return mixed The default value to return if the option does not exist in the database.
248     */
249    public static function default_option_waf_ip_allow_list( $default, $option, $passed_default ) {
250        // Allow get_option() to override this default value
251        if ( $passed_default ) {
252            return $default;
253        }
254
255        $waf_allow_list = '';
256
257        // If the brute force option exists, use that and flag that the WAF needs to be updated.
258        $brute_force_allow_list = Jetpack_Options::get_raw_option( 'jetpack_protect_whitelist', false );
259        if ( false !== $brute_force_allow_list ) {
260            $waf_allow_list = self::merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list );
261            update_option( Waf_Initializer::NEEDS_UPDATE_OPTION_NAME, true );
262        }
263
264        return $waf_allow_list;
265    }
266
267    /**
268     * Check if the brute force protection code is being run by an older version of Jetpack (< 12.0).
269     *
270     * @since 0.11.1
271     *
272     * @return bool
273     */
274    public static function is_brute_force_running_in_jetpack() {
275        return defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '12', '<' );
276    }
277
278    /**
279     * Default the allow list enabled option to the value of the generic IP lists enabled option it replaced.
280     *
281     * @since 0.17.0
282     *
283     * @param mixed  $default         The default value to return if the option does not exist in the database.
284     * @param string $option          Option name.
285     * @param bool   $passed_default  Was get_option() passed a default value.
286     *
287     * @return mixed The default value to return if the option does not exist in the database.
288     */
289    public static function default_option_waf_ip_allow_list_enabled( $default, $option, $passed_default ) {
290        // Allow get_option() to override this default value
291        if ( $passed_default ) {
292            return $default;
293        }
294
295        // If the deprecated IP lists option was set to false, disable the allow list.
296        // @phan-suppress-next-line PhanDeprecatedClassConstant -- Needed for backwards compatibility.
297        $deprecated_option = Jetpack_Options::get_raw_option( Waf_Rules_Manager::IP_LISTS_ENABLED_OPTION_NAME, true );
298        if ( ! $deprecated_option ) {
299            return false;
300        }
301
302        // If the allow list is empty, disable the allow list.
303        if ( ! Jetpack_Options::get_raw_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ) ) {
304            return false;
305        }
306
307        // Default to enabling the allow list.
308        return true;
309    }
310
311    /**
312     * Default the block list enabled option to the value of the generic IP lists enabled option it replaced.
313     *
314     * @since 0.17.0
315     *
316     * @param mixed  $default         The default value to return if the option does not exist in the database.
317     * @param string $option          Option name.
318     * @param bool   $passed_default  Was get_option() passed a default value.
319     *
320     * @return mixed The default value to return if the option does not exist in the database.
321     */
322    public static function default_option_waf_ip_block_list_enabled( $default, $option, $passed_default ) {
323        // Allow get_option() to override this default value
324        if ( $passed_default ) {
325            return $default;
326        }
327
328        // @phan-suppress-next-line PhanDeprecatedClassConstant -- Needed for backwards compatibility.
329        return Jetpack_Options::get_raw_option( Waf_Rules_Manager::IP_LISTS_ENABLED_OPTION_NAME, false );
330    }
331}