Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.18% covered (warning)
66.18%
45 / 68
28.57% covered (danger)
28.57%
4 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Waf_Compatibility
66.18% covered (warning)
66.18%
45 / 68
28.57% covered (danger)
28.57%
4 / 14
71.62
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%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 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        if ( empty( $brute_force_allow_list ) ) {
159            return $waf_allow_list;
160        }
161
162        // Convert the IP objects to strings.
163        $brute_force_allow_list = array_map(
164            function ( $ip_object ) {
165                if ( ! empty( $ip_object->range ) ) {
166                    return $ip_object->range_low . '-' . $ip_object->range_high;
167                }
168
169                return $ip_object->ip_address;
170            },
171            $brute_force_allow_list
172        );
173
174        $brute_force_allow_list_string = implode( "\n", $brute_force_allow_list );
175
176        if ( empty( $waf_allow_list ) ) {
177            return $brute_force_allow_list_string;
178        }
179
180        // Return the lists merged into a single string.
181        return "$waf_allow_list\n$brute_force_allow_list_string";
182    }
183
184    /**
185     * Migrate the brute force protection IP allow list option to the WAF option.
186     *
187     * @since 0.11.0
188     *
189     * @return void
190     */
191    public static function migrate_brute_force_protection_ip_allow_list() {
192        // Get the allow list values directly from the database to avoid filters.
193        $brute_force_allow_list = Jetpack_Options::get_raw_option( 'jetpack_protect_whitelist' );
194        $waf_allow_list         = Jetpack_Options::get_raw_option( 'jetpack_waf_ip_allow_list' );
195
196        if ( ! empty( $brute_force_allow_list ) ) {
197
198            if ( empty( $waf_allow_list ) ) {
199                $waf_allow_list = '';
200            }
201
202            // Merge the two allow lists.
203            $merged_allow_list = self::merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list );
204
205            // Update the WAF IP allow list with the merged list.
206            Jetpack_Options::update_raw_option( 'jetpack_waf_ip_allow_list', $merged_allow_list );
207
208            // Delete the old option if the update was successful.
209            // Check the values directly as `update_raw_option()` returns false if the value hasn't changed.
210            if ( Jetpack_Options::get_raw_option( 'jetpack_waf_ip_allow_list' ) === $merged_allow_list ) {
211                delete_option( 'jetpack_protect_whitelist' );
212            }
213        }
214    }
215
216    /**
217     * Filter for Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME's option value.
218     * Merges the deprecated IP allow list from the brute force protection module
219     * with the existing option value, and flags that the WAF needs to be updated.
220     *
221     * @since 0.11.0
222     *
223     * @param array $waf_allow_list The current value of the option.
224     *
225     * @return array The merged IP allow list.
226     */
227    public static function filter_option_waf_ip_allow_list( $waf_allow_list ) {
228        $brute_force_allow_list = Jetpack_Options::get_raw_option( 'jetpack_protect_whitelist', false );
229        if ( false !== $brute_force_allow_list ) {
230            $waf_allow_list = self::merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list );
231            update_option( Waf_Initializer::NEEDS_UPDATE_OPTION_NAME, true );
232        }
233
234        return $waf_allow_list;
235    }
236
237    /**
238     * Default option for when the Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME option is not set.
239     *
240     * @param mixed  $default         The default value to return if the option does not exist in the database.
241     * @param string $option          Option name.
242     * @param bool   $passed_default  Was get_option() passed a default value.
243     *
244     * @return mixed The default value to return if the option does not exist in the database.
245     */
246    public static function default_option_waf_ip_allow_list( $default, $option, $passed_default ) {
247        // Allow get_option() to override this default value
248        if ( $passed_default ) {
249            return $default;
250        }
251
252        $waf_allow_list = '';
253
254        // If the brute force option exists, use that and flag that the WAF needs to be updated.
255        $brute_force_allow_list = Jetpack_Options::get_raw_option( 'jetpack_protect_whitelist', false );
256        if ( false !== $brute_force_allow_list ) {
257            $waf_allow_list = self::merge_ip_allow_lists( $waf_allow_list, $brute_force_allow_list );
258            update_option( Waf_Initializer::NEEDS_UPDATE_OPTION_NAME, true );
259        }
260
261        return $waf_allow_list;
262    }
263
264    /**
265     * Check if the brute force protection code is being run by an older version of Jetpack (< 12.0).
266     *
267     * @since 0.11.1
268     *
269     * @return bool
270     */
271    public static function is_brute_force_running_in_jetpack() {
272        return defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '12', '<' );
273    }
274
275    /**
276     * Default the allow list enabled option to the value of the generic IP lists enabled option it replaced.
277     *
278     * @since 0.17.0
279     *
280     * @param mixed  $default         The default value to return if the option does not exist in the database.
281     * @param string $option          Option name.
282     * @param bool   $passed_default  Was get_option() passed a default value.
283     *
284     * @return mixed The default value to return if the option does not exist in the database.
285     */
286    public static function default_option_waf_ip_allow_list_enabled( $default, $option, $passed_default ) {
287        // Allow get_option() to override this default value
288        if ( $passed_default ) {
289            return $default;
290        }
291
292        // If the deprecated IP lists option was set to false, disable the allow list.
293        // @phan-suppress-next-line PhanDeprecatedClassConstant -- Needed for backwards compatibility.
294        $deprecated_option = Jetpack_Options::get_raw_option( Waf_Rules_Manager::IP_LISTS_ENABLED_OPTION_NAME, true );
295        if ( ! $deprecated_option ) {
296            return false;
297        }
298
299        // If the allow list is empty, disable the allow list.
300        if ( ! Jetpack_Options::get_raw_option( Waf_Rules_Manager::IP_ALLOW_LIST_OPTION_NAME ) ) {
301            return false;
302        }
303
304        // Default to enabling the allow list.
305        return true;
306    }
307
308    /**
309     * Default the block list enabled option to the value of the generic IP lists enabled option it replaced.
310     *
311     * @since 0.17.0
312     *
313     * @param mixed  $default         The default value to return if the option does not exist in the database.
314     * @param string $option          Option name.
315     * @param bool   $passed_default  Was get_option() passed a default value.
316     *
317     * @return mixed The default value to return if the option does not exist in the database.
318     */
319    public static function default_option_waf_ip_block_list_enabled( $default, $option, $passed_default ) {
320        // Allow get_option() to override this default value
321        if ( $passed_default ) {
322            return $default;
323        }
324
325        // @phan-suppress-next-line PhanDeprecatedClassConstant -- Needed for backwards compatibility.
326        return Jetpack_Options::get_raw_option( Waf_Rules_Manager::IP_LISTS_ENABLED_OPTION_NAME, false );
327    }
328}