Changeset 3393700
- Timestamp:
- 11/11/2025 01:39:21 PM (4 months ago)
- Location:
- keyless-auth
- Files:
-
- 71 added
- 7 edited
-
tags/3.2.3 (added)
-
tags/3.2.3/LICENSE (added)
-
tags/3.2.3/assets (added)
-
tags/3.2.3/assets/css (added)
-
tags/3.2.3/assets/css/2fa-frontend.css (added)
-
tags/3.2.3/assets/css/admin-style.css (added)
-
tags/3.2.3/assets/css/forms-enhanced-dark.css (added)
-
tags/3.2.3/assets/css/forms-enhanced-light.css (added)
-
tags/3.2.3/assets/css/forms-enhanced.css (added)
-
tags/3.2.3/assets/css/help-page.css (added)
-
tags/3.2.3/assets/css/style-back-end.css (added)
-
tags/3.2.3/assets/css/style-front-end.css (added)
-
tags/3.2.3/assets/css/woocommerce-integration.css (added)
-
tags/3.2.3/assets/js (added)
-
tags/3.2.3/assets/js/2fa-frontend.js (added)
-
tags/3.2.3/assets/js/admin-script.js (added)
-
tags/3.2.3/assets/js/help-page.js (added)
-
tags/3.2.3/assets/js/qrcode.js (added)
-
tags/3.2.3/assets/js/qrcode.min.js (added)
-
tags/3.2.3/assets/js/woocommerce-integration.js (added)
-
tags/3.2.3/assets/logo_150_150.png (added)
-
tags/3.2.3/autoload.php (added)
-
tags/3.2.3/includes (added)
-
tags/3.2.3/includes/Admin (added)
-
tags/3.2.3/includes/Admin/Admin.php (added)
-
tags/3.2.3/includes/Admin/Admin.php.backup (added)
-
tags/3.2.3/includes/Admin/Ajax (added)
-
tags/3.2.3/includes/Admin/Ajax/TwoFAAjaxHandler.php (added)
-
tags/3.2.3/includes/Admin/Ajax/index.php (added)
-
tags/3.2.3/includes/Admin/Assets (added)
-
tags/3.2.3/includes/Admin/Assets/AssetLoader.php (added)
-
tags/3.2.3/includes/Admin/Assets/index.php (added)
-
tags/3.2.3/includes/Admin/MenuManager.php (added)
-
tags/3.2.3/includes/Admin/Pages (added)
-
tags/3.2.3/includes/Admin/Pages/DashboardPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/HelpPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/MailLogsPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/OptionsPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/SmtpPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/TemplatesPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/TwoFAUsersPage.php (added)
-
tags/3.2.3/includes/Admin/Pages/index.php (added)
-
tags/3.2.3/includes/Admin/Settings (added)
-
tags/3.2.3/includes/Admin/Settings/SettingsManager.php (added)
-
tags/3.2.3/includes/Admin/Settings/index.php (added)
-
tags/3.2.3/includes/Admin/index.php (added)
-
tags/3.2.3/includes/Core (added)
-
tags/3.2.3/includes/Core/Core.php (added)
-
tags/3.2.3/includes/Core/Database.php (added)
-
tags/3.2.3/includes/Core/Main.php (added)
-
tags/3.2.3/includes/Core/Notices.php (added)
-
tags/3.2.3/includes/Core/PasswordReset.php (added)
-
tags/3.2.3/includes/Core/WooCommerce.php (added)
-
tags/3.2.3/includes/Core/index.php (added)
-
tags/3.2.3/includes/Email (added)
-
tags/3.2.3/includes/Email/MailLogger.php (added)
-
tags/3.2.3/includes/Email/SMTP.php (added)
-
tags/3.2.3/includes/Email/Templates.php (added)
-
tags/3.2.3/includes/Email/index.php (added)
-
tags/3.2.3/includes/Security (added)
-
tags/3.2.3/includes/Security/TwoFA (added)
-
tags/3.2.3/includes/Security/TwoFA/Core.php (added)
-
tags/3.2.3/includes/Security/TwoFA/Frontend.php (added)
-
tags/3.2.3/includes/Security/TwoFA/TOTP.php (added)
-
tags/3.2.3/includes/Security/TwoFA/index.php (added)
-
tags/3.2.3/includes/Security/index.php (added)
-
tags/3.2.3/includes/index.php (added)
-
tags/3.2.3/keyless-auth.php (added)
-
tags/3.2.3/languages (added)
-
tags/3.2.3/languages/keyless-auth.pot (added)
-
tags/3.2.3/readme.txt (added)
-
trunk/includes/Admin/Admin.php (modified) (1 diff)
-
trunk/includes/Core/Core.php (modified) (8 diffs)
-
trunk/includes/Core/Database.php (modified) (4 diffs)
-
trunk/includes/Core/Notices.php (modified) (5 diffs)
-
trunk/includes/Security/TwoFA/Core.php (modified) (9 diffs)
-
trunk/keyless-auth.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
keyless-auth/trunk/includes/Admin/Admin.php
r3380037 r3393700 66 66 if (wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'chrmrtns_kla_learn_more_dismiss_notification')) { 67 67 update_option('chrmrtns_kla_learn_more_dismiss_notification', true); 68 wp_ redirect(remove_query_arg(array('chrmrtns_kla_learn_more_dismiss_notification', '_wpnonce')));68 wp_safe_redirect(remove_query_arg(array('chrmrtns_kla_learn_more_dismiss_notification', '_wpnonce'))); 69 69 exit; 70 70 } -
keyless-auth/trunk/includes/Core/Core.php
r3390737 r3393700 522 522 // Check admin approval compatibility 523 523 if ($this->is_admin_approval_required($user)) { 524 wp_ redirect(add_query_arg('chrmrtns_kla_adminapp_error', '1', $this->get_current_page_url()));524 wp_safe_redirect(add_query_arg('chrmrtns_kla_adminapp_error', '1', $this->get_current_page_url())); 525 525 exit; 526 526 } … … 718 718 } 719 719 720 // Perform the redirect 721 wp_ redirect($redirect_url);720 // Perform the redirect with validation 721 wp_safe_redirect(wp_validate_redirect($redirect_url, wp_login_url())); 722 722 exit; 723 723 } … … 755 755 } 756 756 757 // Perform the redirect 758 wp_ redirect($redirect_url);757 // Perform the redirect with validation 758 wp_safe_redirect(wp_validate_redirect($redirect_url, wp_login_url())); 759 759 exit; 760 760 } … … 776 776 // Validate token 777 777 if (!$this->validate_login_token($user_id, $token)) { 778 wp_ redirect(add_query_arg('chrmrtns_kla_error_token', '1', $this->get_current_page_url()));778 wp_safe_redirect(add_query_arg('chrmrtns_kla_error_token', '1', $this->get_current_page_url())); 779 779 exit; 780 780 } … … 828 828 ), home_url()); 829 829 830 wp_ redirect($tfa_verify_url);830 wp_safe_redirect($tfa_verify_url); 831 831 exit; 832 832 } elseif ($role_required) { … … 846 846 // If grace period expired, redirect to 2FA setup 847 847 if (time() > $grace_end) { 848 wp_ redirect(add_query_arg(array('action' => 'keyless-2fa-setup', 'magic_login' => '1'), home_url()));848 wp_safe_redirect(add_query_arg(array('action' => 'keyless-2fa-setup', 'magic_login' => '1'), home_url())); 849 849 exit; 850 850 } … … 878 878 879 879 $redirect_url = apply_filters('chrmrtns_kla_after_login_redirect', $redirect_url, $user_id); 880 wp_ redirect($redirect_url);880 wp_safe_redirect(wp_validate_redirect($redirect_url, admin_url())); 881 881 exit; 882 882 } … … 1239 1239 1240 1240 // Redirect back to wp-login.php with success message 1241 wp_ redirect(add_query_arg(array(1241 wp_safe_redirect(add_query_arg(array( 1242 1242 'chrmrtns_kla_sent' => '1' 1243 1243 ), wp_login_url())); -
keyless-auth/trunk/includes/Core/Database.php
r3382345 r3393700 82 82 if (!$new_table_exists) { 83 83 // Rename old table to new table name (faster than copy + delete) 84 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Renaming table during migration, table names are safely constructed84 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Renaming table during migration, table names are safely constructed from prefix 85 85 $wpdb->query("RENAME TABLE `{$old_table}` TO `{$new_table}`"); 86 86 } else { 87 87 // If both exist, copy data from old to new, then drop old 88 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Migration data copy, table names are safely constructed88 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Migration data copy, table names are safely constructed from prefix 89 89 $wpdb->query("INSERT IGNORE INTO `{$new_table}` SELECT * FROM `{$old_table}`"); 90 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dropping old table after migration, table name is safely constructed90 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Dropping old table after migration, table name is safely constructed from prefix 91 91 $wpdb->query("DROP TABLE IF EXISTS `{$old_table}`"); 92 92 } … … 306 306 LIMIT %d OFFSET %d"; 307 307 308 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql contains sanitized dynamic content, $where_values properly prepared309 return $wpdb->get_results($wpdb->prepare($sql, $where_values)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching308 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql contains sanitized dynamic content, $where_values properly prepared 309 return $wpdb->get_results($wpdb->prepare($sql, $where_values)); 310 310 } 311 311 … … 458 458 459 459 if (!empty($where_values)) { 460 return $wpdb->query($wpdb->prepare($sql, $where_values)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching 460 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is safely constructed, $where_values properly prepared 461 return $wpdb->query($wpdb->prepare($sql, $where_values)); 461 462 } else { 462 return $wpdb->query($sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching 463 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is safely constructed with only hardcoded WHERE clause 464 return $wpdb->query($sql); 463 465 } 464 466 } … … 774 776 $query = $base_query . " WHERE (u.user_login LIKE %s OR u.user_email LIKE %s OR u.display_name LIKE %s) ORDER BY u.user_login ASC"; 775 777 776 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Querying custom devices table for admin interface with search, query properly prepared with placeholders778 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Querying custom devices table for admin interface with search, query properly prepared with placeholders 777 779 $prepared_query = $wpdb->prepare($query, $search_term, $search_term, $search_term); 778 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query already prepared with placeholders above780 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query already prepared with placeholders above 779 781 $results = $wpdb->get_results($prepared_query); 780 782 } else { 781 783 $query = $base_query . " ORDER BY u.user_login ASC"; 782 784 783 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Querying custom devices table for admin interface, no placeholders needed785 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Querying custom devices table for admin interface, no placeholders needed 784 786 $results = $wpdb->get_results($query); 785 787 } -
keyless-auth/trunk/includes/Core/Notices.php
r3380037 r3393700 44 44 45 45 $user_id = $current_user->ID; 46 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 46 47 do_action($this->notificationId . '_before_notification_displayed', $current_user, $pagenow); 47 48 … … 49 50 // Check that the user hasn't already clicked to ignore the message 50 51 if (!get_user_meta($user_id, $this->notificationId . '_dismiss_notification')) { 52 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 51 53 $finalMessage = apply_filters( 52 $this->notificationId . '_notification_message', 54 $this->notificationId . '_notification_message', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 53 55 '<div class="' . esc_attr($this->notificationClass) . '">' . wp_kses_post($this->notificationMessage) . '</div>', 54 56 $this->notificationMessage … … 56 58 echo wp_kses_post($finalMessage); 57 59 } 60 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 58 61 do_action($this->notificationId . '_notification_displayed', $current_user, $pagenow); 59 62 } 63 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 60 64 do_action($this->notificationId . '_after_notification_displayed', $current_user, $pagenow); 61 65 } … … 72 76 $user_id = $current_user->ID; 73 77 78 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 74 79 do_action($this->notificationId . '_before_notification_dismissed', $current_user); 75 80 … … 79 84 } 80 85 86 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Internal prefixed hook with dynamic name 81 87 do_action($this->notificationId . '_after_notification_dismissed', $current_user); 82 88 } -
keyless-auth/trunk/includes/Security/TwoFA/Core.php
r3382345 r3393700 212 212 $login_url = class_exists('Chrmrtns\\KeylessAuth\\Admin\\Admin') ? 213 213 \Chrmrtns\KeylessAuth\Admin\Admin::get_login_url() : wp_login_url(); 214 wp_ redirect($login_url);214 wp_safe_redirect($login_url); 215 215 exit; 216 216 } … … 222 222 $login_url = class_exists('Chrmrtns\\KeylessAuth\\Admin\\Admin') ? 223 223 \Chrmrtns\KeylessAuth\Admin\Admin::get_login_url() : wp_login_url(); 224 wp_ redirect($login_url);224 wp_safe_redirect($login_url); 225 225 exit; 226 226 } … … 352 352 public function show_2fa_setup_page() { 353 353 if (current_user_can('read')) { 354 wp_ redirect(admin_url('?chrmrtns_kla_setup_notice=1'));354 wp_safe_redirect(admin_url('?chrmrtns_kla_setup_notice=1')); 355 355 exit; 356 356 } … … 370 370 371 371 if (empty($code)) { 372 wp_ redirect(add_query_arg(array('action' => 'keyless-2fa-verify', 'error' => 'empty_code'), home_url()));372 wp_safe_redirect(add_query_arg(array('action' => 'keyless-2fa-verify', 'error' => 'empty_code'), home_url())); 373 373 exit; 374 374 } … … 395 395 wp_set_current_user($user_id, $user->user_login); 396 396 wp_set_auth_cookie($user_id, true); 397 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core WordPress hook 397 398 do_action('wp_login', $user->user_login, $user); 398 399 … … 409 410 } 410 411 411 wp_ redirect($magic_login_data['redirect_url']);412 wp_safe_redirect(wp_validate_redirect($magic_login_data['redirect_url'], admin_url())); 412 413 exit; 413 414 } 414 415 415 wp_ redirect(admin_url());416 wp_safe_redirect(admin_url()); 416 417 exit; 417 418 } else { 418 419 $lockout_seconds = $this->totp->is_user_locked_out($user_id); 419 420 $error = $lockout_seconds > 0 ? 'locked_out' : 'invalid_code'; 420 wp_ redirect(add_query_arg(array('action' => 'keyless-2fa-verify', 'error' => $error), home_url()));421 wp_safe_redirect(add_query_arg(array('action' => 'keyless-2fa-verify', 'error' => $error), home_url())); 421 422 exit; 422 423 } … … 660 661 $_SESSION['chrmrtns_kla_2fa_user_id'] = $user_id; 661 662 662 wp_ redirect(home_url('/?action=keyless-2fa-verify'));663 wp_safe_redirect(home_url('/?action=keyless-2fa-verify')); 663 664 exit; 664 665 } … … 695 696 if (time() > $grace_end) { 696 697 wp_logout(); 697 wp_ redirect(add_query_arg('chrmrtns_kla_2fa_required', '1', wp_login_url()));698 wp_safe_redirect(add_query_arg('chrmrtns_kla_2fa_required', '1', wp_login_url())); 698 699 exit; 699 700 } … … 723 724 724 725 if (time() > $grace_end) { 725 wp_ redirect(home_url('/?action=keyless-2fa-setup'));726 wp_safe_redirect(home_url('/?action=keyless-2fa-setup')); 726 727 exit; 727 728 } -
keyless-auth/trunk/keyless-auth.php
r3390737 r3393700 4 4 * Plugin URI: https://github.com/chrmrtns/keyless-auth 5 5 * Description: Enhanced passwordless authentication with magic email links, two-factor authentication, SMTP integration, WooCommerce integration, and comprehensive security features for WordPress. 6 * Version: 3.2. 26 * Version: 3.2.3 7 7 * Author: Chris Martens 8 8 * Author URI: https://github.com/chrmrtns … … 38 38 39 39 // Define plugin constants 40 define('CHRMRTNS_KLA_VERSION', '3.2. 1');40 define('CHRMRTNS_KLA_VERSION', '3.2.3'); 41 41 define('CHRMRTNS_KLA_PLUGIN_DIR', plugin_dir_path(__FILE__)); 42 42 define('CHRMRTNS_KLA_PLUGIN_URL', plugin_dir_url(__FILE__)); -
keyless-auth/trunk/readme.txt
r3390737 r3393700 6 6 Requires at least: 3.9 7 7 Tested up to: 6.8 8 Stable tag: 3.2. 28 Stable tag: 3.2.3 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 315 315 316 316 == Changelog == 317 318 = 3.2.3 = 319 * SECURITY: Replaced all wp_redirect() with wp_safe_redirect() for enhanced security (21 occurrences) 320 * SECURITY: Added wp_validate_redirect() validation for custom/external URLs with fallback to safe defaults 321 * FIX: WordPress Plugin Check compliance - All redirect security warnings resolved 322 * FIX: Core hook "wp_login" properly ignored with phpcs comment (WordPress core hook, not plugin hook) 323 * IMPROVEMENT: Dynamic notification hooks properly documented with phpcs ignore comments 324 * TECHNICAL: Custom redirect URLs from options now validated before redirect 325 * TECHNICAL: Magic login redirect URLs from transients validated with fallback to admin_url() 326 * TECHNICAL: All 4 files updated: Core.php (8 fixes), TwoFA/Core.php (11 fixes), Admin/Admin.php (1 fix), Notices.php (6 comments) 317 327 318 328 = 3.2.2 =
Note: See TracChangeset
for help on using the changeset viewer.