updated plugin WP-WebAuthn version 1.4.1
This commit is contained in:
@ -1,4 +1,8 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// WordPress transient adapter
|
||||
function wwa_set_temp_val($name, $value, $client_id){
|
||||
return set_transient('wwa_'.$name.$client_id, serialize($value), 90);
|
||||
@ -6,7 +10,7 @@ function wwa_set_temp_val($name, $value, $client_id){
|
||||
|
||||
function wwa_get_temp_val($name, $client_id){
|
||||
$val = get_transient('wwa_'.$name.$client_id);
|
||||
return $val === false ? false : unserialize($val);
|
||||
return $val === false ? false : maybe_unserialize($val);
|
||||
}
|
||||
|
||||
function wwa_delete_temp_val($name, $client_id){
|
||||
@ -37,6 +41,10 @@ function wwa_init_new_options(){
|
||||
if(wwa_get_option('allow_authenticator_type') === false){
|
||||
wwa_update_option('allow_authenticator_type', 'none');
|
||||
}
|
||||
// Existing installs default to 'true' to preserve previous behaviour
|
||||
if(wwa_get_option('show_authenticator_type') === false){
|
||||
wwa_update_option('show_authenticator_type', 'true');
|
||||
}
|
||||
if(wwa_get_option('remember_me') === false){
|
||||
wwa_update_option('remember_me', 'false');
|
||||
}
|
||||
@ -52,6 +60,12 @@ function wwa_init_new_options(){
|
||||
if(wwa_get_option('after_user_registration') === false){
|
||||
wwa_update_option('after_user_registration', 'none');
|
||||
}
|
||||
if(wwa_get_option('terminology') === false){
|
||||
wwa_update_option('terminology', 'webauthn');
|
||||
}
|
||||
if(wwa_get_option('ror_origins') === false){
|
||||
wwa_update_option('ror_origins', '');
|
||||
}
|
||||
}
|
||||
|
||||
// Create random strings for user ID
|
||||
@ -80,7 +94,7 @@ function wwa_add_log($id, $content = '', $init = false){
|
||||
if($log === false){
|
||||
$log = array();
|
||||
}
|
||||
$log[] = '['.current_time('mysql').']['.$id.'] '.$content;
|
||||
$log[] = '['.current_time('mysql').']['.$id.'] '.wp_strip_all_tags($content);
|
||||
update_option('wwa_log', $log);
|
||||
}
|
||||
|
||||
@ -104,49 +118,72 @@ function wwa_generate_call_trace($exception = false){
|
||||
return "Traceback:\n ".implode("\n ", $result);
|
||||
}
|
||||
|
||||
// Delete all credentials when deleting user
|
||||
function wwa_cleanup_blog_credentials($user_id, $blog_id){
|
||||
global $wpdb;
|
||||
$wpdb->delete($wpdb->wwa_credentials, array(
|
||||
'user_id' => $user_id,
|
||||
'registered_blog_id' => $blog_id
|
||||
));
|
||||
}
|
||||
|
||||
function wwa_cleanup_all_user_credentials($user_id){
|
||||
global $wpdb;
|
||||
$wpdb->delete($wpdb->wwa_credentials, array('user_id' => $user_id));
|
||||
delete_user_meta($user_id, 'wwa_user_handle');
|
||||
delete_user_meta($user_id, 'wwa_webauthn_only');
|
||||
}
|
||||
|
||||
function wwa_delete_user($user_id){
|
||||
$res_id = wwa_generate_random_string(5);
|
||||
|
||||
$user_data = get_userdata($user_id);
|
||||
$all_user_meta = wwa_get_option('user_id');
|
||||
$user_key = '';
|
||||
|
||||
// Delete user meta
|
||||
foreach($all_user_meta as $user => $id){
|
||||
if($user === $user_data->user_login){
|
||||
$user_key = $id;
|
||||
wwa_add_log($res_id, "Delete user_key => \"".$id."\"");
|
||||
unset($all_user_meta[$user]);
|
||||
}
|
||||
if($user_data !== false){
|
||||
wwa_add_log($res_id, "Deleted user credentials for => \"".$user_data->user_login."\"");
|
||||
}
|
||||
|
||||
// Delete credentials
|
||||
$all_credentials_meta = json_decode(wwa_get_option('user_credentials_meta'), true);
|
||||
$all_credentials = json_decode(wwa_get_option('user_credentials'), true);
|
||||
foreach($all_credentials_meta as $credential => $meta){
|
||||
if($user_key === $meta['user']){
|
||||
wwa_add_log($res_id, "Delete credential => \"".$credential."\"");
|
||||
unset($all_credentials_meta[$credential]);
|
||||
unset($all_credentials[$credential]);
|
||||
}
|
||||
if(is_multisite()){
|
||||
wwa_cleanup_blog_credentials($user_id, get_current_blog_id());
|
||||
}else{
|
||||
wwa_cleanup_all_user_credentials($user_id);
|
||||
}
|
||||
wwa_update_option('user_id', $all_user_meta);
|
||||
wwa_update_option('user_credentials_meta', wp_json_encode($all_credentials_meta));
|
||||
wwa_update_option('user_credentials', wp_json_encode($all_credentials));
|
||||
wwa_add_log($res_id, "Deleted user => \"".$user_data->user_login."\"");
|
||||
}
|
||||
add_action('delete_user', 'wwa_delete_user');
|
||||
|
||||
function wwa_delete_user_multisite($user_id){
|
||||
$res_id = wwa_generate_random_string(5);
|
||||
|
||||
$user_data = get_userdata($user_id);
|
||||
if($user_data !== false){
|
||||
wwa_add_log($res_id, "Deleted all user credentials for => \"".$user_data->user_login."\" (network deletion)");
|
||||
}
|
||||
|
||||
wwa_cleanup_all_user_credentials($user_id);
|
||||
}
|
||||
add_action('wpmu_delete_user', 'wwa_delete_user_multisite');
|
||||
|
||||
function wwa_remove_user_from_blog($user_id, $blog_id){
|
||||
$res_id = wwa_generate_random_string(5);
|
||||
|
||||
$user_data = get_userdata($user_id);
|
||||
if($user_data !== false){
|
||||
wwa_add_log($res_id, "Deleted user credentials for => \"".$user_data->user_login."\" (removed from blog ".$blog_id.")");
|
||||
}
|
||||
|
||||
wwa_cleanup_blog_credentials($user_id, $blog_id);
|
||||
}
|
||||
add_action('remove_user_from_blog', 'wwa_remove_user_from_blog', 10, 2);
|
||||
|
||||
// Add CSS and JS in login page
|
||||
function wwa_login_js(){
|
||||
wwa_init_new_options();
|
||||
|
||||
$wwa_not_allowed = false;
|
||||
if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){
|
||||
$wwa_not_allowed = true;
|
||||
}
|
||||
wp_enqueue_script('wwa_login', plugins_url('js/login.js', __FILE__), array(), get_option('wwa_version')['version'], true);
|
||||
$first_choice = wwa_get_option('first_choice');
|
||||
wp_localize_script('wwa_login', 'php_vars', array(
|
||||
wp_localize_script('wwa_login', 'wwa_login_php_vars', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'admin_url' => admin_url(),
|
||||
'usernameless' => (wwa_get_option('usernameless_login') === false ? 'false' : wwa_get_option('usernameless_login')),
|
||||
@ -156,8 +193,9 @@ function wwa_login_js(){
|
||||
'webauthn_only' => ($first_choice === 'webauthn' && !$wwa_not_allowed) ? 'true' : 'false',
|
||||
'password_reset' => ((wwa_get_option('password_reset') === false || wwa_get_option('password_reset') === 'off') ? 'false' : 'true'),
|
||||
'separator' => apply_filters('login_link_separator', ' | '),
|
||||
'terminology' => (wwa_get_option('terminology') === false ? 'passkey' : wwa_get_option('terminology')),
|
||||
'i18n_1' => __('Auth', 'wp-webauthn'),
|
||||
'i18n_2' => __('Authenticate with WebAuthn', 'wp-webauthn'),
|
||||
'i18n_2' => wwa_get_option('terminology') === 'webauthn' ? __('Authenticate with WebAuthn', 'wp-webauthn') : __('Authenticate with a passkey', 'wp-webauthn'),
|
||||
'i18n_3' => __('Hold on...', 'wp-webauthn'),
|
||||
'i18n_4' => __('Please proceed...', 'wp-webauthn'),
|
||||
'i18n_5' => __('Authenticating...', 'wp-webauthn'),
|
||||
@ -167,7 +205,9 @@ function wwa_login_js(){
|
||||
'i18n_9' => __('Username', 'wp-webauthn'),
|
||||
'i18n_10' => __('Username or Email Address'),
|
||||
'i18n_11' => __('<strong>Error</strong>: The username field is empty.', 'wp-webauthn'),
|
||||
'i18n_12' => '<span class="wwa-try-username">'.__('Try to enter the username', 'wp-webauthn').'</span>'
|
||||
'i18n_12' => '<span class="wwa-try-username">'.__('Try to enter the username', 'wp-webauthn').'</span>',
|
||||
'i18n_13' => __('Password'),
|
||||
'i18n_14' => wwa_get_option('terminology') === 'webauthn' ? 'WebAuthn' : __('Passkey', 'wp-webauthn')
|
||||
));
|
||||
if($first_choice === 'true' || $first_choice === 'webauthn'){
|
||||
wp_enqueue_script('wwa_default', plugins_url('js/default_wa.js', __FILE__), array(), get_option('wwa_version')['version'], true);
|
||||
@ -187,7 +227,7 @@ function wwa_disable_password($user){
|
||||
if(is_wp_error($user)){
|
||||
return $user;
|
||||
}
|
||||
if(get_the_author_meta('webauthn_only', $user->ID) === 'true'){
|
||||
if(get_user_meta($user->ID, 'wwa_webauthn_only', true) === 'true'){
|
||||
return new WP_Error('wwa_password_disabled_for_account', __('Logging in with password has been disabled for this account.', 'wp-webauthn'));
|
||||
}
|
||||
return $user;
|
||||
@ -240,86 +280,73 @@ if(wwa_get_option('password_reset') === 'admin' || wwa_get_option('password_rese
|
||||
add_filter('allow_password_reset', 'wwa_handle_password');
|
||||
}
|
||||
|
||||
// Show a notice in admin pages
|
||||
function wwa_no_authenticator_warning(){
|
||||
if(is_network_admin()){
|
||||
return;
|
||||
}
|
||||
|
||||
$user_info = wp_get_current_user();
|
||||
$first_choice = wwa_get_option('first_choice');
|
||||
$check_self = true;
|
||||
if($first_choice !== 'webauthn' && get_the_author_meta('webauthn_only', $user_info->ID ) !== 'true'){
|
||||
if($first_choice !== 'webauthn' && get_user_meta($user_info->ID, 'wwa_webauthn_only', true) !== 'true'){
|
||||
$check_self = false;
|
||||
}
|
||||
|
||||
if($check_self){
|
||||
// Check current user
|
||||
$user_id = '';
|
||||
$show_notice_flag = false;
|
||||
if(!isset(wwa_get_option('user_id')[$user_info->user_login])){
|
||||
$show_notice_flag = true;
|
||||
}else{
|
||||
$user_id = wwa_get_option('user_id')[$user_info->user_login];
|
||||
}
|
||||
|
||||
if(!$show_notice_flag){
|
||||
$show_notice_flag = true;
|
||||
$data = json_decode(wwa_get_option('user_credentials_meta'), true);
|
||||
foreach($data as $value){
|
||||
if($user_id === $value['user']){
|
||||
$show_notice_flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($show_notice_flag){?>
|
||||
global $wpdb;
|
||||
$count = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->wwa_credentials}
|
||||
WHERE user_id = %d AND registered_blog_id = %d",
|
||||
$user_info->ID, get_current_blog_id()
|
||||
));
|
||||
if(intval($count) === 0){ ?>
|
||||
<div class="notice notice-warning">
|
||||
<?php /* translators: %s: 'the site' or 'your account', and admin profile url */ ?>
|
||||
<p><?php printf(__('Logging in with password has been disabled for %s but you haven\'t register any WebAuthn authenticator yet. You may unable to login again once you log out. <a href="%s#wwa-webauthn-start">Register</a>', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'), admin_url('profile.php'));?></p>
|
||||
<?php
|
||||
$wwa_scope_label = esc_html($first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'));
|
||||
$wwa_cred_label = esc_html(wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticator', 'wp-webauthn') : __('passkey', 'wp-webauthn'));
|
||||
/* translators: %1$s: 'the site' or 'your account', %2$s: 'WebAuthn authenticator' or 'passkey', %3$s: admin profile url */
|
||||
?>
|
||||
<p><?php echo wp_kses(sprintf(__('Logging in with password has been disabled for %1$s but you haven\'t register any %2$s on the current site yet. You may unable to login again once you log out. <a href="%3$s#wwa-webauthn-start">Register</a>', 'wp-webauthn'), $wwa_scope_label, $wwa_cred_label, esc_url(admin_url('profile.php'))), array('a' => array('href' => array())));?></p>
|
||||
<?php if(is_multisite() && !is_subdomain_install()){
|
||||
/* translators: %s: 'WebAuthn authenticators' or 'Passkeys' */ ?>
|
||||
<p><?php echo esc_html(sprintf(__('%s registered on other sites within this network may also be used to log in.', 'wp-webauthn'), wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticators', 'wp-webauthn') : __('Passkeys', 'wp-webauthn'))); ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
// Check other user
|
||||
|
||||
global $pagenow;
|
||||
if($pagenow == 'user-edit.php' && isset($_GET['user_id']) && intval($_GET['user_id']) !== $user_info->ID){
|
||||
$user_id_wp = intval($_GET['user_id']);
|
||||
if($user_id_wp <= 0){
|
||||
if($user_id_wp <= 0 || !current_user_can('edit_user', $user_id_wp)){
|
||||
return;
|
||||
}
|
||||
if(!current_user_can('edit_user', $user_id_wp)){
|
||||
$other_user = get_user_by('id', $user_id_wp);
|
||||
if($other_user === false){
|
||||
return;
|
||||
}
|
||||
$user_info = get_user_by('id', $user_id_wp);
|
||||
|
||||
if($user_info === false){
|
||||
if($first_choice !== 'webauthn' && get_user_meta($other_user->ID, 'wwa_webauthn_only', true) !== 'true'){
|
||||
return;
|
||||
}
|
||||
|
||||
if($first_choice !== 'webauthn' && get_the_author_meta('webauthn_only', $user_info->ID) !== 'true'){
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = '';
|
||||
$show_notice_flag = false;
|
||||
if(!isset(wwa_get_option('user_id')[$user_info->user_login])){
|
||||
$show_notice_flag = true;
|
||||
}else{
|
||||
$user_id = wwa_get_option('user_id')[$user_info->user_login];
|
||||
}
|
||||
|
||||
if(!$show_notice_flag){
|
||||
$show_notice_flag = true;
|
||||
$data = json_decode(wwa_get_option('user_credentials_meta'), true);
|
||||
foreach($data as $value){
|
||||
if($user_id === $value['user']){
|
||||
$show_notice_flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($show_notice_flag){ ?>
|
||||
global $wpdb;
|
||||
$count = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->wwa_credentials}
|
||||
WHERE user_id = %d AND registered_blog_id = %d",
|
||||
$other_user->ID, get_current_blog_id()
|
||||
));
|
||||
if(intval($count) === 0){ ?>
|
||||
<div class="notice notice-warning">
|
||||
<?php /* translators: %s: 'the site' or 'your account' */ ?>
|
||||
<p><?php printf(__('Logging in with password has been disabled for %s but <strong>this account</strong> haven\'t register any WebAuthn authenticator yet. This user may unable to login.', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('this account', 'wp-webauthn'));?></p>
|
||||
<?php
|
||||
$wwa_scope_label = esc_html($first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('this account', 'wp-webauthn'));
|
||||
$wwa_cred_label = esc_html(wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticator', 'wp-webauthn') : __('passkey', 'wp-webauthn'));
|
||||
/* translators: %1$s: 'the site' or 'this account', %2$s: 'WebAuthn authenticator' or 'passkey' */
|
||||
?>
|
||||
<p><?php echo wp_kses(sprintf(__('Logging in with password has been disabled for %1$s but <strong>this account</strong> haven\'t register any %2$s on the current site yet. This user may unable to login.', 'wp-webauthn'), $wwa_scope_label, $wwa_cred_label), array('strong' => array()));?></p>
|
||||
<?php if(is_multisite() && !is_subdomain_install()){
|
||||
/* translators: %s: 'WebAuthn authenticators' or 'Passkeys' */ ?>
|
||||
<p><?php echo esc_html(sprintf(__('%s registered on other sites within this network may also be used to log in.', 'wp-webauthn'), wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticators', 'wp-webauthn') : __('Passkeys', 'wp-webauthn'))); ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
@ -353,17 +380,27 @@ function wwa_settings_link($links_array, $plugin_file_name){
|
||||
}
|
||||
add_filter('plugin_action_links', 'wwa_settings_link', 10, 2);
|
||||
|
||||
function wwa_network_settings_link($links_array, $plugin_file_name){
|
||||
if($plugin_file_name === 'wp-webauthn/wp-webauthn.php'){
|
||||
$links_array[] = '<a href="'.esc_url(network_admin_url('settings.php?page=wwa_network_admin')).'">'.__('Network Settings', 'wp-webauthn').'</a>';
|
||||
}
|
||||
return $links_array;
|
||||
}
|
||||
if(is_multisite()){
|
||||
add_filter('network_admin_plugin_action_links', 'wwa_network_settings_link', 10, 2);
|
||||
}
|
||||
|
||||
function wwa_meta_link($links_array, $plugin_file_name){
|
||||
if($plugin_file_name === 'wp-webauthn/wp-webauthn.php'){
|
||||
$links_array[] = '<a href="https://github.com/yrccondor/wp-webauthn">'.__('GitHub', 'wp-webauthn').'</a>';
|
||||
$links_array[] = '<a href="http://doc.flyhigher.top/wp-webauthn">'.__('Documentation', 'wp-webauthn').'</a>';
|
||||
$links_array[] = '<a href="https://doc.flyhigher.top/wp-webauthn">'.__('Documentation', 'wp-webauthn').'</a>';
|
||||
}
|
||||
return $links_array;
|
||||
}
|
||||
add_filter('plugin_row_meta', 'wwa_meta_link', 10, 2);
|
||||
|
||||
// Check if we are under HTTPS
|
||||
function wwa_check_ssl() {
|
||||
function wwa_check_ssl(){
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '') {
|
||||
return true;
|
||||
}
|
||||
@ -380,17 +417,37 @@ function wwa_check_ssl() {
|
||||
}
|
||||
|
||||
// Check user privileges
|
||||
function wwa_validate_privileges() {
|
||||
$user = wp_get_current_user();
|
||||
$allowed_roles = array('administrator');
|
||||
if(array_intersect($allowed_roles, $user->roles)){
|
||||
return true;
|
||||
function wwa_validate_privileges(){
|
||||
return current_user_can('manage_options');
|
||||
}
|
||||
|
||||
// Get Related Origins Request list
|
||||
function wwa_get_ror_list(){
|
||||
$raw = wwa_get_option('ror_origins');
|
||||
if($raw === false || $raw === ''){
|
||||
return array();
|
||||
}
|
||||
return false;
|
||||
$origins = array();
|
||||
$lines = explode("\n", $raw);
|
||||
foreach($lines as $line){
|
||||
$line = trim($line);
|
||||
if($line === ''){
|
||||
continue;
|
||||
}
|
||||
$parsed = wp_parse_url($line);
|
||||
if(isset($parsed['scheme']) && isset($parsed['host'])){
|
||||
$origin = $parsed['scheme'] . '://' . $parsed['host'];
|
||||
if(isset($parsed['port'])){
|
||||
$origin .= ':' . $parsed['port'];
|
||||
}
|
||||
$origins[] = $origin;
|
||||
}
|
||||
}
|
||||
return $origins;
|
||||
}
|
||||
|
||||
// Get user by username or email
|
||||
function wwa_get_user($username) {
|
||||
function wwa_get_user($username){
|
||||
if(wwa_get_option('email_login') !== 'true'){
|
||||
return get_user_by('login', $username);
|
||||
}else{
|
||||
@ -400,3 +457,54 @@ function wwa_get_user($username) {
|
||||
return get_user_by('login', $username);
|
||||
}
|
||||
}
|
||||
|
||||
// Provide plugin version for other plugins
|
||||
function wwa_loaded_version(){
|
||||
if(!get_option('wwa_version')){
|
||||
return '0.0.1';
|
||||
}
|
||||
return get_option('wwa_version')['version'];
|
||||
}
|
||||
|
||||
// Register query vars
|
||||
function wwa_query_vars($vars) {
|
||||
$vars[] = 'wwa-well-known-ror';
|
||||
return $vars;
|
||||
}
|
||||
|
||||
// Add rewrite rules for .well-known/webauthn
|
||||
function wwa_add_rewrite_rules() {
|
||||
add_rewrite_rule('^\.well-known/webauthn$', 'index.php?wwa-well-known-ror=true', 'top');
|
||||
}
|
||||
function wwa_apply_rewrite_rules() {
|
||||
wwa_add_rewrite_rules();
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
// Handle .well-known/webauthn
|
||||
function wwa_handle_ror($wp) {
|
||||
if (array_key_exists('wwa-well-known-ror', $wp->query_vars)) {
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
echo wp_json_encode(array(
|
||||
'origins'=> wwa_get_ror_list()
|
||||
));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize plugin data for a new site created in a multisite network
|
||||
function wwa_new_site_init($new_site){
|
||||
$network_active = get_site_option('active_sitewide_plugins');
|
||||
if(isset($network_active['wp-webauthn/wp-webauthn.php'])){
|
||||
switch_to_blog($new_site->id);
|
||||
wwa_init_data();
|
||||
wwa_apply_rewrite_rules();
|
||||
restore_current_blog();
|
||||
}
|
||||
}
|
||||
add_action('wp_initialize_site', 'wwa_new_site_init');
|
||||
|
||||
add_filter('query_vars', 'wwa_query_vars');
|
||||
add_action('parse_request', 'wwa_handle_ror', 99);
|
||||
add_action('init', 'wwa_add_rewrite_rules', 1);
|
||||
|
||||
Reference in New Issue
Block a user