-
Notifications
You must be signed in to change notification settings - Fork 100
/
two-factor.php
266 lines (217 loc) · 8.84 KB
/
two-factor.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
<?php
/**
* Plugin Name: VIP Force Two Factor
* Description: Force Two Factor Authentication for stronger security.
* Author: Automattic
* License: GPL version 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
// Two Factor is incompatible with core tests (phpunit/tests/admin/includesPlugin.php)
return;
}
// Custom list of providers
require_once __DIR__ . '/wpcom-vip-two-factor/set-providers.php';
// Detect if the current user is logged in via Jetpack SSO
require_once __DIR__ . '/wpcom-vip-two-factor/is-jetpack-sso.php';
// Do not allow API requests from 2fa users.
add_filter( 'two_factor_user_api_login_enable', '__return_false', 1 ); // Hook in early to allow overrides
function wpcom_vip_should_force_two_factor() {
// Don't force 2FA by default in local environments
if ( ! WPCOM_IS_VIP_ENV && ! apply_filters( 'wpcom_vip_is_two_factor_local_testing', false ) ) {
return false;
}
// The proxy is the second factor for VIP Support users
if ( is_proxied_automattician() ) {
return false;
}
// The Two Factor plugin wasn't loaded for some reason.
if ( ! class_exists( 'Two_Factor_Core' ) ) {
return false;
}
if ( apply_filters( 'wpcom_vip_is_user_using_two_factor', false ) ) {
return false;
}
// Don't force 2FA for Jetpack SSO users that have Two-step enabled
if ( \Automattic\VIP\TwoFactor\is_jetpack_sso_two_step() ) {
return false;
}
// If it's a request attempting to connect a local user to a
// WordPress.com user via XML-RPC or REST, allow it through.
if ( wpcom_vip_is_jetpack_authorize_request() ) {
return false;
}
// Allow custom SSO solutions
if ( wpcom_vip_use_custom_sso() ) {
return false;
}
return true;
}
function wpcom_vip_use_custom_sso() {
$custom_sso_enabled = apply_filters( 'wpcom_vip_use_custom_sso', null );
if ( null !== $custom_sso_enabled ) {
return $custom_sso_enabled;
}
// Check for OneLogin SSO
if ( function_exists( 'is_saml_enabled' ) && is_saml_enabled() ) {
return true;
}
// Check for SimpleSaml
if ( function_exists( '\HumanMade\SimpleSaml\instance' ) && \HumanMade\SimpleSaml\instance() ) {
return true;
}
return false;
}
function wpcom_vip_is_jetpack_authorize_request() {
return (
// XML-RPC Jetpack authorize request
// This works with the classic core XML-RPC endpoint, but not
// Jetpack's alternate endpoint.
defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST
&& isset( $_GET['for'] ) && 'jetpack' === $_GET['for'] // phpcs:ignore WordPress.Security.NonceVerification.Recommended
&& isset( $GLOBALS['wp_xmlrpc_server'], $GLOBALS['wp_xmlrpc_server']->message, $GLOBALS['wp_xmlrpc_server']->message->methodName )
&& 'jetpack.remoteAuthorize' === $GLOBALS['wp_xmlrpc_server']->message->methodName
) || (
// REST Jetpack authorize request
defined( 'REST_REQUEST' ) && REST_REQUEST
&& isset( $GLOBALS['wp_rest_server'] )
&& wpcom_vip_is_jetpack_authorize_rest_request()
);
}
/**
* Setter/Getter to keep track of whether the current request is a REST
* API request for /jetpack/v4/remote_authorize request that connects a
* WordPress.com user to a local user.
*/
function wpcom_vip_is_jetpack_authorize_rest_request( $set = null ) {
static $is_jetpack_authorize_rest_request = false;
if ( ! is_null( $set ) ) {
$is_jetpack_authorize_rest_request = $set;
}
return $is_jetpack_authorize_rest_request;
}
/**
* Hooked to the `rest_request_before_callbacks` filter to keep track of
* whether the current request is a REST API request for
* /jetpack/v4/remote_authorize request that connects WordPress.com user
* to a local user.
* @return unmodified - it's attached to a filter.
*/
function wpcom_vip_is_jetpack_authorize_rest_request_hook( $response, $handler ) {
if ( isset( $handler['callback'] ) && 'Jetpack_Core_Json_Api_Endpoints::remote_authorize' === $handler['callback'] ) {
wpcom_vip_is_jetpack_authorize_rest_request( true );
}
return $response;
}
add_filter( 'rest_request_before_callbacks', 'wpcom_vip_is_jetpack_authorize_rest_request_hook', 10, 2 );
function wpcom_vip_is_two_factor_forced() {
if ( ! wpcom_vip_should_force_two_factor() ) {
return false;
}
return apply_filters( 'wpcom_vip_is_two_factor_forced', false );
}
function wpcom_vip_enforce_two_factor_plugin() {
if ( is_user_logged_in() ) {
$cap = apply_filters( 'wpcom_vip_two_factor_enforcement_cap', 'manage_options' );
$limited = current_user_can( $cap );
// Calculate current_user_can outside map_meta_cap to avoid callback loop
add_filter( 'wpcom_vip_is_two_factor_forced', function () use ( $limited ) {
return $limited;
}, 9 );
// Calcuate two factor authentication support outside map_meta_cap to avoid callback loop
// see: https://github.com/Automattic/vip-go-mu-plugins/pull/1445#issuecomment-592124810
$is_user_using_two_factor = Two_Factor_Core::is_user_using_two_factor();
add_filter(
'wpcom_vip_is_user_using_two_factor',
function () use ( $is_user_using_two_factor ) {
return $is_user_using_two_factor;
}
);
add_action( 'admin_notices', 'wpcom_vip_two_factor_admin_notice' );
add_filter( 'map_meta_cap', 'wpcom_vip_two_factor_filter_caps', 0, 4 );
}
}
if ( ! defined( 'WP_INSTALLING' ) ) {
add_action( 'muplugins_loaded', 'wpcom_enable_two_factor_plugin' );
}
function wpcom_enable_two_factor_plugin() {
$enable_two_factor = apply_filters( 'wpcom_vip_enable_two_factor', true );
if ( true !== $enable_two_factor ) {
return;
}
// We loaded the two-factor plugin using wpcom_vip_load_plugin but that skips when skip-plugins is set.
// Switching to require_once so it no longer gets skipped
require_once WPVIP_MU_PLUGIN_DIR . '/shared-plugins/two-factor/two-factor.php';
add_action( 'set_current_user', 'wpcom_vip_enforce_two_factor_plugin' );
}
/**
* Filter Caps
*
* Remove caps for users without two-factor enabled so they are treated as a Contributor.
*/
function wpcom_vip_two_factor_filter_caps( $caps, $cap, $user_id, $args ) {
// If the machine user is not defined or the current user is not the machine user, don't filter caps.
if ( ( ! defined( 'WPCOM_VIP_MACHINE_USER_ID' ) || WPCOM_VIP_MACHINE_USER_ID !== $user_id ) && wpcom_vip_is_two_factor_forced() ) {
// Use a hard-coded list of caps that give just enough access to set up 2FA
$subscriber_caps = [
'read',
'level_0',
];
// You can edit your own user account (required to set up 2FA)
if ( 'edit_user' === $cap && ! empty( $args ) && (int) $user_id === (int) $args[0] ) {
$subscriber_caps[] = 'edit_user';
}
// WooCommerce caps to check
$woocommerce_caps = [
'edit_posts',
'manage_woocommerce',
'view_admin_dashboard',
];
// Track whether or not we've already granted this user wp-admin access based on WC standards.
static $user_should_have_wc_admin_access = false;
// If we haven't granted access yet, and this $cap is a WC cap to check.
if ( ! $user_should_have_wc_admin_access && in_array( $cap, $woocommerce_caps ) ) {
// If this user has this $cap and it's `true`, grant this user wp-admin access.
if ( isset( wp_get_current_user()->allcaps[ $cap ] ) && true === wp_get_current_user()->allcaps[ $cap ] ) {
$user_should_have_wc_admin_access = true;
add_filter( 'woocommerce_prevent_admin_access', '__return_false' );
}
}
if ( ! in_array( $cap, $subscriber_caps, true ) ) {
return array( 'do_not_allow' );
}
}
return $caps;
}
function wpcom_vip_should_show_notice_on_current_screen() {
$screen = get_current_screen();
// Don't show on the "Edit Post" screen as it interferes with the Block Editor.
if ( $screen->is_block_editor() ) {
return false;
}
return true;
}
function wpcom_vip_two_factor_admin_notice() {
if ( ! wpcom_vip_is_two_factor_forced() ) {
return;
}
if ( ! wpcom_vip_should_show_notice_on_current_screen() ) {
return;
}
?>
<div id="vip-2fa-error" class="notice-error wrap clearfix" style="align-items: center;background: #ffffff;border-left-width:4px;border-left-style:solid;border-radius: 6px;display: flex;margin-top: 30px;padding: 30px;line-height: 2em;">
<div class="dashicons dashicons-warning" style="display:flex;float:left;margin-right:2rem;font-size:38px;align-items:center;margin-left:-20px;color:#ffb900;"></div>
<div>
<p style="font-weight:bold; font-size:16px;">
Your account requires <a href="https://docs.wpvip.com/security/two-factor-authentication/">two-factor authentication</a> to be enabled.
</p>
<p>For the safety and security of this site, your account access has been downgraded. Please enable two-factor authentication to restore your access.</p>
<p>
<a href="<?php echo esc_url( admin_url( 'profile.php#two-factor-options' ) ); ?>" class="button button-primary">
Enable Two-factor Authentication
</a>
<a href="https://wpvip.com/documentation/vip-go/two-factor-authentication-on-vip-go/" class="button" target="_blank">Learn More</a>
</p>
</div>
</div>
<?php
}