-
Notifications
You must be signed in to change notification settings - Fork 9
/
class-wporg-webauthn-provider.php
181 lines (152 loc) · 5.47 KB
/
class-wporg-webauthn-provider.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
<?php
namespace WordPressdotorg\Two_Factor;
use TwoFactor_Provider_WebAuthn, Two_Factor_Core;
use function WordPressdotorg\Two_Factor\{ after_provider_setup, after_provider_deactivated };
/**
* Extends the TwoFactor_Provider_WebAuthn class for WordPress.org needs.
*/
class WPORG_TwoFactor_Provider_WebAuthn extends TwoFactor_Provider_WebAuthn {
static $use_caching = true;
/**
* Use the parent class as the "key" in the Two Factor UI.
*/
public function get_key() {
return parent::class;
}
/**
* Override the parent constructor to allow for adding filters early.
*
* This would be ideal as a `__construct()` method, but the parent is final and $instance is private.
*/
public static function get_instance() {
static $instance = false;
if ( ! $instance ) {
$instance = new static();
}
// Add the custom filters needed for this class
static $filters_added = false;
if ( ! $filters_added ) {
$filters_added = true;
$instance->_add_filters();
}
return $instance;
}
/**
* Check if the provider is available for the given user.
*
* This method includes caching for WordPress.org, as it's called on most pageloads.
*/
public function is_available_for_user( $user ) {
$is_available = wp_cache_get( 'webauthn:' . $user->ID, 'users', false, $found );
if ( $found && self::$use_caching ) {
return $is_available;
}
$is_available = parent::is_available_for_user( $user );
wp_cache_set( 'webauthn:' . $user->ID, $is_available, 'users', HOUR_IN_SECONDS );
return $is_available;
}
/**
* See https://github.com/sjinks/wp-two-factor-provider-webauthn/pull/468
*
* @return string
*/
public function get_alternative_provider_label() {
return __( 'Use your security key', 'wporg-two-factor' );
}
/**
* Add some filters to watch for WebAuthn events.
*/
public function _add_filters() {
// Clear the cache when a user is updated.
add_action( 'wp_ajax_webauthn_preregister', [ $this, '_webauthn_ajax_request' ], 1 );
add_action( 'wp_ajax_webauthn_register', [ $this, '_webauthn_ajax_request' ], 1 );
add_action( 'wp_ajax_webauthn_delete_key', [ $this, '_webauthn_ajax_request' ], 1 );
add_action( 'wp_ajax_webauthn_rename_key', [ $this, '_webauthn_ajax_request' ], 1 );
// Disable the admin UI if it needs revalidation.
add_action( 'show_user_security_settings', [ $this, '_show_user_security_settings' ], -1 );
// Disable EdDSA support for keys, to enable Android NFC to work with modern keys.
add_action( 'wp_ajax_webauthn_preregister', [ $this, '_remove_eddsa_alg' ], 1 );
// Extend the session revalidation after registering a new key.
add_action( 'wp_ajax_webauthn_register', [ $this, '_extend_revalidation' ], 1 );
// Deactivate the session 2fa status after deleting keys if they don't have TOTP setup.
add_action( 'wp_ajax_webauthn_delete_key', [ $this, '_delete_key' ], 1 );
}
/**
* Force the user to revalidate their 2FA if they're updating their WebAuthn keys.
*
* This is pending an upstream PR for the revalidation.
*/
public function _webauthn_ajax_request() {
// Disable caching while we're making WebAuthn requests.
self::$use_caching = false;
// Check the users session is still active and 2FA revalidation isn't required.
if ( ! Two_Factor_Core::current_user_can_update_two_factor_options() ) {
wp_send_json_error( 'Your session has expired. Please refresh the page and try again.' );
}
// Clear the caches for this class after the request is finished.
add_action( 'shutdown', [ $this, '_clear_cache' ] );
}
/**
* Wrap the additional providers in a disabled fieldset if the user needs to revalidate.
*
* This is pending an upstream PR.
*/
public function _show_user_security_settings() {
$show_2fa_options = Two_Factor_Core::current_user_can_update_two_factor_options();
if ( ! $show_2fa_options ) {
// TODO: Perhaps the Core UI should extend it's `<fieldset>` to wrap the additional providers?
echo '<fieldset disabled="disabled">';
add_action( 'show_user_security_settings', function() { echo '</fieldset>'; }, 1001 );
}
}
/**
* Extend the session revalidation after registering a new key.
*/
public function _extend_revalidation() {
ob_start( function( $output ) {
$json = json_decode( $output, true );
if ( ! empty( $json['success'] ) ) {
after_provider_setup( $_REQUEST['user_id'], $this );
}
return $output;
} );
}
/**
* Fire the deactivation callback once a key is deleted.
*/
public function _delete_key() {
ob_start( function( $output ) {
$json = json_decode( $output, true );
if ( ! empty( $json['success'] ) ) {
after_provider_deactivated( $_REQUEST['user_id'], $this );
}
return $output;
} );
}
/**
* Resolve Android NFC Security Key issues when a newer key is registered through a desktop client.
*
* This disables EdDSA (aka. ES25519) support, which Android NFC appears to lack.
*
* @see https://github.com/sjinks/wp-two-factor-provider-webauthn/issues/221#issuecomment-1539543124
*/
public function _remove_eddsa_alg() {
ob_start( function( $output ) {
$json = json_decode( $output );
if ( $json && ! empty( $json->data->options->pubKeyCredParams ) ) {
$json->data->options->pubKeyCredParams = array_values(
wp_list_filter(
$json->data->options->pubKeyCredParams,
[ 'alg' => -8 ],
'NOT'
)
);
$output = wp_json_encode( $json );
}
return $output;
} );
}
public function _clear_cache() {
wp_cache_delete( 'webauthn:' . get_current_user_id(), 'users' );
}
}