Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Force_2FA
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 6
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 plugins_loaded
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 admin_notice
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 force_2fa
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
30
 jetpack_set_two_step
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 get_login_error_message
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Force Jetpack 2FA Functionality
4 *
5 * Ported from original repo at https://github.com/automattic/jetpack-force-2fa
6 *
7 * @package automattic/jetpack-connection
8 */
9
10namespace Automattic\Jetpack\Connection\SSO;
11
12use Automattic\Jetpack\Connection\SSO;
13use Automattic\Jetpack\Modules;
14use WP_Error;
15
16/**
17 * Force users to use two-factor authentication.
18 *
19 * @phan-constructor-used-for-side-effects
20 */
21class Force_2FA {
22    /**
23     * The role to force 2FA for.
24     *
25     * Defaults to manage_options via the plugins_loaded function.
26     * Can be modified with the jetpack_force_2fa_cap filter.
27     *
28     * @var string
29     */
30    private $role;
31
32    /**
33     * Constructor.
34     */
35    public function __construct() {
36        add_action( 'after_setup_theme', array( $this, 'plugins_loaded' ) );
37    }
38
39    /**
40     * Load the plugin via the plugins_loaded hook.
41     */
42    public function plugins_loaded() {
43        /**
44         * Filter the role to force 2FA for.
45         * Defaults to manage_options.
46         *
47         * @param string $role The role to force 2FA for.
48         * @return string
49         * @since jetpack-12.7
50         * @module SSO
51         */
52        $this->role = apply_filters( 'jetpack_force_2fa_cap', 'manage_options' );
53
54        // Bail if Jetpack SSO is not active
55        if ( ! ( new Modules() )->is_active( 'sso' ) ) {
56            add_action( 'admin_notices', array( $this, 'admin_notice' ) );
57            return;
58        }
59
60        $this->force_2fa();
61    }
62
63    /**
64     * Display an admin notice if Jetpack SSO is not active.
65     */
66    public function admin_notice() {
67        /**
68         * Filter if an admin notice is deplayed when Force 2FA is required, but SSO is not enabled.
69         * Defaults to true.
70         *
71         * @param bool $display_notice Whether to display the notice.
72         * @return bool
73         * @since jetpack-12.7
74         * @module SSO
75         */
76        if ( apply_filters( 'jetpack_force_2fa_dependency_notice', true ) && current_user_can( $this->role ) ) {
77            wp_admin_notice(
78                esc_html__( 'Jetpack Force 2FA requires Jetpack’s SSO feature.', 'jetpack-connection' ),
79                array(
80                    'type' => 'warning',
81                )
82            );
83        }
84    }
85
86    /**
87     * Force 2FA when using Jetpack SSO and force Jetpack SSO.
88     *
89     * @return void
90     */
91    private function force_2fa() {
92        // Allows WP.com login to a local account if it matches the local account.
93        add_filter( 'jetpack_sso_match_by_email', '__return_true', 9999 );
94
95        // multisite
96        if ( is_multisite() ) {
97
98            // Hide the login form
99            add_filter( 'jetpack_remove_login_form', '__return_true', 9999 );
100            add_filter( 'jetpack_sso_bypass_login_forward_wpcom', '__return_true', 9999 );
101            add_filter( 'jetpack_sso_display_disclaimer', '__return_false', 9999 );
102
103            add_filter(
104                'wp_authenticate_user',
105                function () {
106                    return new WP_Error( 'wpcom-required', $this->get_login_error_message() ); },
107                9999
108            );
109
110            add_filter( 'jetpack_sso_require_two_step', '__return_true' );
111
112            add_filter( 'allow_password_reset', '__return_false' );
113        } else {
114            // Not multisite.
115
116            // Completely disable the standard login form for admins.
117            add_filter(
118                'wp_authenticate_user',
119                function ( $user ) {
120                    if ( is_wp_error( $user ) ) {
121                        return $user;
122                    }
123                    if ( $user->has_cap( $this->role ) ) {
124                        return new WP_Error( 'wpcom-required', $this->get_login_error_message(), $user->user_login );
125                    }
126                    return $user;
127                },
128                9999
129            );
130
131            add_filter(
132                'allow_password_reset',
133                function ( $allow, $user_id ) {
134                    if ( user_can( $user_id, $this->role ) ) {
135                        return false;
136                    }
137                    return $allow; },
138                9999,
139                2
140            );
141
142            add_action( 'jetpack_sso_pre_handle_login', array( $this, 'jetpack_set_two_step' ) );
143        }
144    }
145
146    /**
147     * Specifically set the two step filter for Jetpack SSO.
148     *
149     * @param Object $user_data The user data from WordPress.com.
150     *
151     * @return void
152     */
153    public function jetpack_set_two_step( $user_data ) {
154        $user = SSO::get_user_by_wpcom_id( $user_data->ID );
155
156        // Borrowed from Jetpack. Ignores the match_by_email setting.
157        if ( empty( $user ) ) {
158            $user = get_user_by( 'email', $user_data->email );
159        }
160
161        if ( $user && $user->has_cap( $this->role ) ) {
162            add_filter( 'jetpack_sso_require_two_step', '__return_true' );
163        }
164    }
165
166    /**
167     * Get the login error message.
168     *
169     * @return string
170     */
171    private function get_login_error_message() {
172        /**
173         * Filter the login error message.
174         * Defaults to a message that explains the user must use a WordPress.com account with 2FA enabled.
175         *
176         * @param string $message The login error message.
177         * @return string
178         * @since jetpack-12.7
179         * @module SSO
180         */
181        return apply_filters(
182            'jetpack_force_2fa_login_error_message',
183            sprintf( 'For added security, please log in using your WordPress.com account.<br /><br />Note: Your account must have <a href="%1$s" target="_blank">Two Step Authentication</a> enabled, which can be configured from <a href="%2$s" target="_blank">Security Settings</a>.', 'https://support.wordpress.com/security/two-step-authentication/', 'https://wordpress.com/me/security/two-step' )
184        );
185    }
186}