Wordfence Login Security - Version 1.0.6

Version Description

  • January 14, 2021 =
  • Improvement: Made a number of WordPress 5.6 and jQuery 3.x compatibility improvements.
  • Improvement: Replaced the terms whitelist and blacklist with allowlist and blocklist.
  • Fix: Sync roles to new sites in multisite configurations
  • Fix: Corrected 2FA config links in notices for multisite
  • Fix: Corrected inactive user count when users with 2FA have been deleted
  • Fix: reCAPTCHA will no longer block requests with missing tokens in test mode
Download this release

Release Info

Developer wfmatt
Plugin Icon 128x128 Wordfence Login Security
Version 1.0.6
Comparing to
See all releases

Code changes from version 1.0.5 to 1.0.6

Files changed (32) hide show
  1. classes/controller/ajax.php +7 -1
  2. classes/controller/captcha.php +10 -1
  3. classes/controller/permissions.php +144 -14
  4. classes/controller/users.php +1 -1
  5. classes/controller/wordfencels.php +11 -8
  6. css/{admin-global.1578941826.css → admin-global.1610634190.css} +0 -0
  7. css/{admin.1578941826.css → admin.1610634190.css} +0 -0
  8. css/{colorbox.1578941826.css → colorbox.1610634190.css} +0 -0
  9. css/{font-awesome.1578941826.css → font-awesome.1610634190.css} +0 -0
  10. css/{ionicons.1578941826.css → ionicons.1610634190.css} +0 -0
  11. css/{jquery-ui-timepicker-addon.1578941826.css → jquery-ui-timepicker-addon.1610634190.css} +0 -0
  12. css/{jquery-ui.min.1578941826.css → jquery-ui.min.1610634190.css} +0 -0
  13. css/{jquery-ui.structure.min.1578941826.css → jquery-ui.structure.min.1610634190.css} +0 -0
  14. css/{jquery-ui.theme.min.1578941826.css → jquery-ui.theme.min.1610634190.css} +0 -0
  15. css/{login.1578941826.css → login.1610634190.css} +0 -0
  16. css/{wfselect2.min.1578941826.css → wfselect2.min.1610634190.css} +0 -0
  17. js/{Chart.bundle.min.1578941826.js → Chart.bundle.min.1610634190.js} +0 -0
  18. js/{admin-global.1578941826.js → admin-global.1610634190.js} +0 -0
  19. js/{admin.1578941826.js → admin.1610634190.js} +0 -0
  20. js/{jquery-ui-timepicker-addon.1578941826.js → jquery-ui-timepicker-addon.1610634190.js} +0 -0
  21. js/{jquery.colorbox.1578941826.js → jquery.colorbox.1610634190.js} +0 -0
  22. js/{jquery.colorbox.min.1578941826.js → jquery.colorbox.min.1610634190.js} +0 -0
  23. js/{jquery.qrcode.min.1578941826.js → jquery.qrcode.min.1610634190.js} +0 -0
  24. js/{jquery.tmpl.min.1578941826.js → jquery.tmpl.min.1610634190.js} +0 -0
  25. js/{login.1578941826.js → login.1610634190.js} +0 -0
  26. js/{wfselect2.min.1578941826.js → wfselect2.min.1610634190.js} +0 -0
  27. readme.txt +11 -3
  28. views/options/option-ip-source.php +1 -1
  29. views/page/manage.php +1 -1
  30. views/page/page.php +1 -0
  31. views/settings/options.php +2 -2
  32. wordfence-login-security.php +3 -3
classes/controller/ajax.php CHANGED
@@ -411,7 +411,7 @@ class Controller_AJAX {
411
  }
412
 
413
  $subject = sprintf(__('2FA will soon be required on %s', 'wordfence-2fa'), home_url());
414
- $message = sprintf(__("You do not currently have two-factor authentication active on your account, which will be required beginning %s.\n\nConfigure 2FA: %s", 'wordfence-2fa'), Controller_Time::format_local_time('F j, Y', Controller_Settings::shared()->get_int(Controller_Settings::OPTION_REQUIRE_2FA_GRACE_PERIOD)), admin_url('admin.php?page=WFLS'));
415
 
416
  $admins = Controller_Users::shared()->admin_users();
417
  $sent = 0;
@@ -420,6 +420,12 @@ class Controller_AJAX {
420
  if (Controller_Users::shared()->has_2fa_active($a)) {
421
  continue;
422
  }
 
 
 
 
 
 
423
 
424
  wp_mail($a->user_email, $subject, $message);
425
  $sent++;
411
  }
412
 
413
  $subject = sprintf(__('2FA will soon be required on %s', 'wordfence-2fa'), home_url());
414
+ $requiredDate = Controller_Time::format_local_time('F j, Y', Controller_Settings::shared()->get_int(Controller_Settings::OPTION_REQUIRE_2FA_GRACE_PERIOD));
415
 
416
  $admins = Controller_Users::shared()->admin_users();
417
  $sent = 0;
420
  if (Controller_Users::shared()->has_2fa_active($a)) {
421
  continue;
422
  }
423
+
424
+ $message = sprintf(
425
+ __("You do not currently have two-factor authentication active on your account, which will be required beginning %s.\n\nConfigure 2FA: %s", 'wordfence-2fa'),
426
+ $requiredDate,
427
+ (is_multisite() && is_super_admin($a->ID)) ? network_admin_url('admin.php?page=WFLS') : admin_url('admin.php?page=WFLS')
428
+ );
429
 
430
  wp_mail($a->user_email, $subject, $message);
431
  $sent++;
classes/controller/captcha.php CHANGED
@@ -58,6 +58,15 @@ class Controller_CAPTCHA {
58
  public function threshold() {
59
  return Controller_Settings::shared()->get_float(Controller_Settings::OPTION_RECAPTCHA_THRESHOLD, 0.5);
60
  }
 
 
 
 
 
 
 
 
 
61
 
62
  /**
63
  * Queries the reCAPTCHA endpoint with the given token, verifies the action matches, and returns the corresponding
@@ -111,7 +120,7 @@ class Controller_CAPTCHA {
111
  * @return bool
112
  */
113
  public function is_human($score) {
114
- if (Controller_Settings::shared()->get_bool(\WordfenceLS\Controller_Settings::OPTION_CAPTCHA_TEST_MODE)) {
115
  return true;
116
  }
117
 
58
  public function threshold() {
59
  return Controller_Settings::shared()->get_float(Controller_Settings::OPTION_RECAPTCHA_THRESHOLD, 0.5);
60
  }
61
+
62
+ /**
63
+ * Determine whether or not test mode for reCAPTCHA is enabled
64
+ *
65
+ * @return bool
66
+ */
67
+ public function test_mode() {
68
+ return Controller_Settings::shared()->get_bool(\WordfenceLS\Controller_Settings::OPTION_CAPTCHA_TEST_MODE);
69
+ }
70
 
71
  /**
72
  * Queries the reCAPTCHA endpoint with the given token, verifies the action matches, and returns the corresponding
120
  * @return bool
121
  */
122
  public function is_human($score) {
123
+ if ($this->test_mode()) {
124
  return true;
125
  }
126
 
classes/controller/permissions.php CHANGED
@@ -6,6 +6,10 @@ class Controller_Permissions {
6
  const CAP_ACTIVATE_2FA_SELF = 'wf2fa_activate_2fa_self'; //Activate 2FA on its own user account
7
  const CAP_ACTIVATE_2FA_OTHERS = 'wf2fa_activate_2fa_others'; //Activate 2FA on user accounts other than its own
8
  const CAP_MANAGE_SETTINGS = 'wf2fa_manage_settings'; //Edit settings for the plugin
 
 
 
 
9
 
10
  /**
11
  * Returns the singleton Controller_Permissions.
@@ -19,11 +23,18 @@ class Controller_Permissions {
19
  }
20
  return $_shared;
21
  }
 
 
 
 
 
 
22
 
23
  public function install() {
 
24
  if (is_multisite()) {
25
  //Super Admin automatically gets all capabilities, so we don't need to explicitly add them
26
- $this->_add_cap_multisite('administrator', self::CAP_ACTIVATE_2FA_SELF);
27
  }
28
  else {
29
  $this->_add_cap('administrator', self::CAP_ACTIVATE_2FA_SELF);
@@ -31,10 +42,129 @@ class Controller_Permissions {
31
  $this->_add_cap('administrator', self::CAP_MANAGE_SETTINGS);
32
  }
33
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  public function allow_2fa_self($role_name) {
 
36
  if (is_multisite()) {
37
- $this->_add_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF);
38
  }
39
  else {
40
  $this->_add_cap($role_name, self::CAP_ACTIVATE_2FA_SELF);
@@ -42,8 +172,9 @@ class Controller_Permissions {
42
  }
43
 
44
  public function disallow_2fa_self($role_name) {
 
45
  if (is_multisite()) {
46
- $this->_remove_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF);
47
  }
48
  else {
49
  if ($role_name == 'administrator') {
@@ -71,21 +202,20 @@ class Controller_Permissions {
71
  }
72
 
73
  //\WP_Roles in WP < 4.9 initializes based on the current blog ID
74
- global $wpdb;
75
- $current_site = $wpdb->set_blog_id($site_id);
76
  $wp_roles = new \WP_Roles();
77
- $wpdb->set_blog_id($current_site);
78
  return $wp_roles;
79
  }
80
 
81
- private function _add_cap_multisite($role_name, $cap) {
82
  global $wpdb;
83
- $blogs = $wpdb->get_col("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0");
84
  foreach ($blogs as $id) {
85
  $wp_roles = $this->_wp_roles($id);
86
- $current_site = $wpdb->set_blog_id($id); //We have to set the blog ID for $wpdb because \WP_Roles does not set it prior to saving a multisite role
87
  $this->_add_cap($role_name, $cap, $wp_roles);
88
- $wpdb->set_blog_id($current_site);
89
  }
90
  }
91
 
@@ -100,14 +230,14 @@ class Controller_Permissions {
100
  return true;
101
  }
102
 
103
- private function _remove_cap_multisite($role_name, $cap) {
104
  global $wpdb;
105
- $blogs = $wpdb->get_col("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0");
106
  foreach ($blogs as $id) {
107
  $wp_roles = $this->_wp_roles($id);
108
- $current_site = $wpdb->set_blog_id($id); //We have to set the blog ID for $wpdb because \WP_Roles does not set it prior to saving a multisite role
109
  $this->_remove_cap($role_name, $cap, $wp_roles);
110
- $wpdb->set_blog_id($current_site);
111
  }
112
  }
113
 
6
  const CAP_ACTIVATE_2FA_SELF = 'wf2fa_activate_2fa_self'; //Activate 2FA on its own user account
7
  const CAP_ACTIVATE_2FA_OTHERS = 'wf2fa_activate_2fa_others'; //Activate 2FA on user accounts other than its own
8
  const CAP_MANAGE_SETTINGS = 'wf2fa_manage_settings'; //Edit settings for the plugin
9
+
10
+ const SITE_BATCH_SIZE = 50; //The maximum number of sites to process during a single request
11
+
12
+ private $network_roles = array();
13
 
14
  /**
15
  * Returns the singleton Controller_Permissions.
23
  }
24
  return $_shared;
25
  }
26
+
27
+ private function on_role_change() {
28
+ update_site_option('wfls_last_role_change', time());
29
+ if(is_multisite())
30
+ update_site_option('wfls_role_batch_position', 0);
31
+ }
32
 
33
  public function install() {
34
+ $this->on_role_change();
35
  if (is_multisite()) {
36
  //Super Admin automatically gets all capabilities, so we don't need to explicitly add them
37
+ $this->_add_cap_multisite('administrator', self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
38
  }
39
  else {
40
  $this->_add_cap('administrator', self::CAP_ACTIVATE_2FA_SELF);
42
  $this->_add_cap('administrator', self::CAP_MANAGE_SETTINGS);
43
  }
44
  }
45
+
46
+ public function init() {
47
+ global $wp_version;
48
+ if(is_multisite()){
49
+ if(version_compare($wp_version, '5.1.0', '>=')){
50
+ add_action('wp_initialize_site', array($this, '_wp_initialize_site'), 99);
51
+ }
52
+ else{
53
+ add_action('wpmu_new_blog', array($this, '_wpmu_new_blog'), 10, 5);
54
+ }
55
+ add_action('init', array($this, 'check_role_sync'), 1);
56
+ }
57
+ }
58
+
59
+ public function _wpmu_new_blog($site_id, $user_id, $domain, $path, $network_id) {
60
+ $this->sync_roles($network_id, $site_id);
61
+ }
62
+
63
+ public function _wp_initialize_site($new_site) {
64
+ $this->sync_roles($new_site->site_id, $new_site->blog_id);
65
+ }
66
+
67
+ public function check_role_sync() {
68
+ //Trigger an initial update for existing installations
69
+ $last_role_change=(int)get_site_option('wfls_last_role_change', 0);
70
+ if($last_role_change===0)
71
+ $this->on_role_change();
72
+ //Process the current batch if necessary
73
+ $position=(int)get_site_option('wfls_role_batch_position', 0);
74
+ if($position===-1)
75
+ return;
76
+ $sites=$this->get_sites($position, self::SITE_BATCH_SIZE);
77
+ if(empty($sites)){
78
+ $position=-1;
79
+ return;
80
+ }
81
+ else{
82
+ $network_id=get_current_site()->id;
83
+ foreach($sites as $site){
84
+ $site=(int)$site;
85
+ $this->sync_roles($network_id, $site);
86
+ }
87
+ $position=$site;
88
+ }
89
+ update_site_option('wfls_role_batch_position', $position);
90
+ //Update the current site if not already up to date
91
+ $site_id=get_current_blog_id();
92
+ if($last_role_change>=get_option('wfls_last_role_sync', 0)&&$site_id>=$position){
93
+ $this->sync_roles(get_current_site()->id, $site_id);
94
+ update_option('wfls_last_role_sync', time());
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Get the primary site ID for a given network
100
+ */
101
+ private function get_primary_site_id($network_id) {
102
+ global $wpdb;
103
+ if(function_exists('get_network')){
104
+ $network=get_network($network_id); //TODO: Support multi-network throughout plugin
105
+ return (int)$network->blog_id;
106
+ }
107
+ else{
108
+ return (int)$wpdb->get_var($wpdb->prepare("SELECT blogs.blog_id FROM {$wpdb->site} sites JOIN {$wpdb->blogs} blogs ON blogs.site_id=sites.id AND blogs.path=sites.path WHERE sites.id=%d", $network_id));
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Get all primary sites in a multi-network setup
114
+ */
115
+ private function get_primary_sites() {
116
+ global $wpdb;
117
+ if(function_exists('get_networks')){
118
+ return array_map(function($network){ return $network->blog_id; }, get_networks());
119
+ }
120
+ else{
121
+ return $wpdb->get_col("SELECT blogs.blog_id FROM {$wpdb->site} sites JOIN {$wpdb->blogs} blogs ON blogs.site_id=sites.id AND blogs.path=sites.path");
122
+ }
123
+ }
124
+
125
+ private function get_sites($from, $count) {
126
+ global $wpdb;
127
+ return $wpdb->get_col($wpdb->prepare("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0 AND blog_id > %d ORDER BY blog_id LIMIT %d", $from, $count));
128
+ }
129
+
130
+ /**
131
+ * Sync role capabilities from the default site to a newly added site
132
+ * @param int $network_id the relevant network
133
+ * @param int $site_id the newly added site(blog)
134
+ */
135
+ private function sync_roles($network_id, $site_id){
136
+ if(array_key_exists($network_id, $this->network_roles)){
137
+ $current_roles=$this->network_roles[$network_id];
138
+ }
139
+ else{
140
+ $current_roles=$this->_wp_roles($this->get_primary_site_id($network_id));
141
+ $this->network_roles[$network_id]=$current_roles;
142
+ }
143
+ $new_site_roles=$this->_wp_roles($site_id);
144
+ $capabilities=array(
145
+ self::CAP_ACTIVATE_2FA_SELF,
146
+ self::CAP_ACTIVATE_2FA_OTHERS,
147
+ self::CAP_MANAGE_SETTINGS
148
+ );
149
+ foreach($current_roles->get_names() as $role_name=>$role_label){
150
+ if($new_site_roles->get_role($role_name)===null)
151
+ $new_site_roles->add_role($role_name, $role_label);
152
+ $role=$current_roles->get_role($role_name);
153
+ foreach($capabilities as $cap){
154
+ if($role->has_cap($cap)){
155
+ $this->_add_cap_multisite($role_name, $cap, array($site_id));
156
+ }
157
+ else{
158
+ $this->_remove_cap_multisite($role_name, $cap, array($site_id));
159
+ }
160
+ }
161
+ }
162
+ }
163
 
164
  public function allow_2fa_self($role_name) {
165
+ $this->on_role_change();
166
  if (is_multisite()) {
167
+ $this->_add_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
168
  }
169
  else {
170
  $this->_add_cap($role_name, self::CAP_ACTIVATE_2FA_SELF);
172
  }
173
 
174
  public function disallow_2fa_self($role_name) {
175
+ $this->on_role_change();
176
  if (is_multisite()) {
177
+ $this->_remove_cap_multisite($role_name, self::CAP_ACTIVATE_2FA_SELF, $this->get_primary_sites());
178
  }
179
  else {
180
  if ($role_name == 'administrator') {
202
  }
203
 
204
  //\WP_Roles in WP < 4.9 initializes based on the current blog ID
205
+ switch_to_blog($site_id);
 
206
  $wp_roles = new \WP_Roles();
207
+ restore_current_blog();
208
  return $wp_roles;
209
  }
210
 
211
+ private function _add_cap_multisite($role_name, $cap, $blog_ids=null) {
212
  global $wpdb;
213
+ $blogs = $blog_ids===null?$wpdb->get_col("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0"):$blog_ids;
214
  foreach ($blogs as $id) {
215
  $wp_roles = $this->_wp_roles($id);
216
+ switch_to_blog($id);
217
  $this->_add_cap($role_name, $cap, $wp_roles);
218
+ restore_current_blog();
219
  }
220
  }
221
 
230
  return true;
231
  }
232
 
233
+ private function _remove_cap_multisite($role_name, $cap, $blog_ids=null) {
234
  global $wpdb;
235
+ $blogs = $blog_ids===null?$wpdb->get_col("SELECT `blog_id` FROM `{$wpdb->blogs}` WHERE `deleted` = 0"):$blog_ids;
236
  foreach ($blogs as $id) {
237
  $wp_roles = $this->_wp_roles($id);
238
+ switch_to_blog($id);
239
  $this->_remove_cap($role_name, $cap, $wp_roles);
240
+ restore_current_blog();
241
  }
242
  }
243
 
classes/controller/users.php CHANGED
@@ -346,7 +346,7 @@ class Controller_Users {
346
  $total_users = (int) $wpdb->get_var("SELECT COUNT(ID) as c FROM {$wpdb->users}");
347
  }
348
  $active_users = $this->active_count();
349
- return array('active_users' => $active_users, 'inactive_users' => max($total_users - $active_users, '-'));
350
  }
351
 
352
  public function detailed_user_counts() {
346
  $total_users = (int) $wpdb->get_var("SELECT COUNT(ID) as c FROM {$wpdb->users}");
347
  }
348
  $active_users = $this->active_count();
349
+ return array('active_users' => $active_users, 'inactive_users' => max($total_users - $active_users, 0));
350
  }
351
 
352
  public function detailed_user_counts() {
classes/controller/wordfencels.php CHANGED
@@ -29,6 +29,7 @@ class Controller_WordfenceLS {
29
  Controller_AJAX::shared()->init();
30
  Controller_Users::shared()->init();
31
  Controller_Time::shared()->init();
 
32
  }
33
 
34
  protected function _init_actions() {
@@ -73,7 +74,7 @@ class Controller_WordfenceLS {
73
  \wfModuleController::shared()->addOptionIndex('wfls-option-allow-remember', __('Login Security: Allow remembering device for 30 days', 'wordfence-2fa'));
74
  \wfModuleController::shared()->addOptionIndex('wfls-option-require-2fa-xml-rpc', __('Login Security: Require 2FA for XML-RPC call authentication', 'wordfence-2fa'));
75
  \wfModuleController::shared()->addOptionIndex('wfls-option-disable-xml-rpc', __('Login Security: Disable XML-RPC authentication', 'wordfence-2fa'));
76
- \wfModuleController::shared()->addOptionIndex('wfls-option-whitelist-2fa', __('Login Security: Whitelisted IP addresses that bypass 2FA', 'wordfence-2fa'));
77
  \wfModuleController::shared()->addOptionIndex('wfls-option-enable-captcha', __('Login Security: Enable reCAPTCHA on the login and user registration pages', 'wordfence-2fa'));
78
 
79
  $title = __('Login Security Options', 'wordfence-ls');
@@ -376,7 +377,7 @@ END
376
 
377
  $performVerification = false;
378
  $token = (isset($_POST['wfls-captcha-token']) && is_string($_POST['wfls-captcha-token']) ? $_POST['wfls-captcha-token'] : '');
379
- if ($requireCAPTCHA && empty($token)) { //No CAPTCHA token means forced additional verification (if 2FA is not active)
380
  $performVerification = true;
381
  }
382
 
@@ -401,7 +402,7 @@ END
401
 
402
  if (!isset($score)) {
403
  $score = Controller_CAPTCHA::shared()->score($token);
404
- if ($score === false) { //An invalid token will require additional verification (if 2FA is not active)
405
  $performVerification = true;
406
  }
407
  }
@@ -587,7 +588,7 @@ END
587
  return new \WP_Error('wfls_twofactor_blocked', __('<strong>LOGIN BLOCKED</strong>: 2FA is required to be active on all administrator accounts.', 'wordfence-2fa'));
588
  }
589
  else if (defined('WFLS_WILL_BE_REQUIRED') && WFLS_WILL_BE_REQUIRED) {
590
- Controller_Notices::shared()->add_notice(Model_Notice::SEVERITY_CRITICAL, new Model_HTML(sprintf(__('You do not currently have two-factor authentication active on your account, which will be required beginning %s. <a href="%s">Configure 2FA</a>', 'wordfence-2fa'), Controller_Time::format_local_time('F j, Y', Controller_Settings::shared()->get_int(Controller_Settings::OPTION_REQUIRE_2FA_GRACE_PERIOD)), esc_url(admin_url('admin.php?page=WFLS')))), 'wfls-will-be-required', $user);
591
  }
592
  }
593
 
@@ -627,16 +628,18 @@ END
627
  */
628
  $requireCAPTCHA = Controller_CAPTCHA::shared()->enabled() && !(defined('XMLRPC_REQUEST') && XMLRPC_REQUEST); //CAPTCHA is enabled, not an XML-RPC request
629
  $requireCAPTCHA = apply_filters('wordfence_ls_require_captcha', $requireCAPTCHA);
630
-
631
- if ($requireCAPTCHA && (!isset($_POST['wfls-captcha-token']) || !is_string($_POST['wfls-captcha-token'])) && !empty($sanitized_user_login)) { //A CAPTCHA token must be provided for the login attempt to proceed past this point
 
 
632
  $errors->add('wfls_captcha_required', __('<strong>REGISTRATION ATTEMPT BLOCKED</strong>: This site requires a security token created when the page loads for all registration attempts. Please ensure JavaScript is enabled and try again.', 'wordfence-ls'));
633
  return;
634
  }
635
 
636
  $score = false;
637
  if ($requireCAPTCHA) {
638
- $score = Controller_CAPTCHA::shared()->score($_POST['wfls-captcha-token']);
639
- if ($score === false) { //The token must be valid
640
  $errors->add('wfls_captcha_required', __('<strong>REGISTRATION ATTEMPT BLOCKED</strong>: The security token for the login attempt was invalid or expired. Please reload the page and try again.', 'wordfence-ls'));
641
  return;
642
  }
29
  Controller_AJAX::shared()->init();
30
  Controller_Users::shared()->init();
31
  Controller_Time::shared()->init();
32
+ Controller_Permissions::shared()->init();
33
  }
34
 
35
  protected function _init_actions() {
74
  \wfModuleController::shared()->addOptionIndex('wfls-option-allow-remember', __('Login Security: Allow remembering device for 30 days', 'wordfence-2fa'));
75
  \wfModuleController::shared()->addOptionIndex('wfls-option-require-2fa-xml-rpc', __('Login Security: Require 2FA for XML-RPC call authentication', 'wordfence-2fa'));
76
  \wfModuleController::shared()->addOptionIndex('wfls-option-disable-xml-rpc', __('Login Security: Disable XML-RPC authentication', 'wordfence-2fa'));
77
+ \wfModuleController::shared()->addOptionIndex('wfls-option-whitelist-2fa', __('Login Security: Allowlisted IP addresses that bypass 2FA', 'wordfence-2fa'));
78
  \wfModuleController::shared()->addOptionIndex('wfls-option-enable-captcha', __('Login Security: Enable reCAPTCHA on the login and user registration pages', 'wordfence-2fa'));
79
 
80
  $title = __('Login Security Options', 'wordfence-ls');
377
 
378
  $performVerification = false;
379
  $token = (isset($_POST['wfls-captcha-token']) && is_string($_POST['wfls-captcha-token']) ? $_POST['wfls-captcha-token'] : '');
380
+ if ($requireCAPTCHA && empty($token) && !Controller_CAPTCHA::shared()->test_mode()) { //No CAPTCHA token means forced additional verification (if neither 2FA nor test mode are active)
381
  $performVerification = true;
382
  }
383
 
402
 
403
  if (!isset($score)) {
404
  $score = Controller_CAPTCHA::shared()->score($token);
405
+ if ($score === false && !Controller_CAPTCHA::shared()->test_mode()) { //An invalid token will require additional verification (if neither 2FA nor test mode are active)
406
  $performVerification = true;
407
  }
408
  }
588
  return new \WP_Error('wfls_twofactor_blocked', __('<strong>LOGIN BLOCKED</strong>: 2FA is required to be active on all administrator accounts.', 'wordfence-2fa'));
589
  }
590
  else if (defined('WFLS_WILL_BE_REQUIRED') && WFLS_WILL_BE_REQUIRED) {
591
+ Controller_Notices::shared()->add_notice(Model_Notice::SEVERITY_CRITICAL, new Model_HTML(sprintf(__('You do not currently have two-factor authentication active on your account, which will be required beginning %s. <a href="%s">Configure 2FA</a>', 'wordfence-2fa'), Controller_Time::format_local_time('F j, Y', Controller_Settings::shared()->get_int(Controller_Settings::OPTION_REQUIRE_2FA_GRACE_PERIOD)), esc_url((is_multisite() && is_super_admin($user->ID)) ? network_admin_url('admin.php?page=WFLS') : admin_url('admin.php?page=WFLS')))), 'wfls-will-be-required', $user);
592
  }
593
  }
594
 
628
  */
629
  $requireCAPTCHA = Controller_CAPTCHA::shared()->enabled() && !(defined('XMLRPC_REQUEST') && XMLRPC_REQUEST); //CAPTCHA is enabled, not an XML-RPC request
630
  $requireCAPTCHA = apply_filters('wordfence_ls_require_captcha', $requireCAPTCHA);
631
+
632
+ $token = (isset($_POST['wfls-captcha-token']) && is_string($_POST['wfls-captcha-token']) ? $_POST['wfls-captcha-token'] : '');
633
+
634
+ if ($requireCAPTCHA && empty($token) && !empty($sanitized_user_login) && !Controller_CAPTCHA::shared()->test_mode()) { //A CAPTCHA token must be provided for the login attempt to proceed past this point except in test mode
635
  $errors->add('wfls_captcha_required', __('<strong>REGISTRATION ATTEMPT BLOCKED</strong>: This site requires a security token created when the page loads for all registration attempts. Please ensure JavaScript is enabled and try again.', 'wordfence-ls'));
636
  return;
637
  }
638
 
639
  $score = false;
640
  if ($requireCAPTCHA) {
641
+ $score = Controller_CAPTCHA::shared()->score($token);
642
+ if ($score === false && !Controller_CAPTCHA::shared()->test_mode()) { //The token must be valid except in test mode
643
  $errors->add('wfls_captcha_required', __('<strong>REGISTRATION ATTEMPT BLOCKED</strong>: The security token for the login attempt was invalid or expired. Please reload the page and try again.', 'wordfence-ls'));
644
  return;
645
  }
css/{admin-global.1578941826.css → admin-global.1610634190.css} RENAMED
File without changes
css/{admin.1578941826.css → admin.1610634190.css} RENAMED
File without changes
css/{colorbox.1578941826.css → colorbox.1610634190.css} RENAMED
File without changes
css/{font-awesome.1578941826.css → font-awesome.1610634190.css} RENAMED
File without changes
css/{ionicons.1578941826.css → ionicons.1610634190.css} RENAMED
File without changes
css/{jquery-ui-timepicker-addon.1578941826.css → jquery-ui-timepicker-addon.1610634190.css} RENAMED
File without changes
css/{jquery-ui.min.1578941826.css → jquery-ui.min.1610634190.css} RENAMED
File without changes
css/{jquery-ui.structure.min.1578941826.css → jquery-ui.structure.min.1610634190.css} RENAMED
File without changes
css/{jquery-ui.theme.min.1578941826.css → jquery-ui.theme.min.1610634190.css} RENAMED
File without changes
css/{login.1578941826.css → login.1610634190.css} RENAMED
File without changes
css/{wfselect2.min.1578941826.css → wfselect2.min.1610634190.css} RENAMED
File without changes
js/{Chart.bundle.min.1578941826.js → Chart.bundle.min.1610634190.js} RENAMED
File without changes
js/{admin-global.1578941826.js → admin-global.1610634190.js} RENAMED
File without changes
js/{admin.1578941826.js → admin.1610634190.js} RENAMED
File without changes
js/{jquery-ui-timepicker-addon.1578941826.js → jquery-ui-timepicker-addon.1610634190.js} RENAMED
File without changes
js/{jquery.colorbox.1578941826.js → jquery.colorbox.1610634190.js} RENAMED
File without changes
js/{jquery.colorbox.min.1578941826.js → jquery.colorbox.min.1610634190.js} RENAMED
File without changes
js/{jquery.qrcode.min.1578941826.js → jquery.qrcode.min.1610634190.js} RENAMED
File without changes
js/{jquery.tmpl.min.1578941826.js → jquery.tmpl.min.1610634190.js} RENAMED
File without changes
js/{login.1578941826.js → login.1610634190.js} RENAMED
File without changes
js/{wfselect2.min.1578941826.js → wfselect2.min.1610634190.js} RENAMED
File without changes
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: wfryan, wfmattr, mmaunder, wfmatt
3
  Tags: security, login security, 2fa, two factor authentication, captcha, xml-rpc, mfa, 2 factor
4
  Requires at least: 4.5
5
  Requires PHP: 5.3
6
- Tested up to: 5.5
7
- Stable tag: 1.0.5
8
 
9
  Secure your website with Wordfence Login Security, providing two-factor authentication, login and registration CAPTCHA, and XML-RPC protection.
10
 
@@ -58,6 +58,14 @@ Secure your website with Wordfence Login Security.
58
 
59
  == Changelog ==
60
 
 
 
 
 
 
 
 
 
61
  = 1.0.5 - January 13, 2020 =
62
  * Changed: AJAX endpoints now send the application/json Content-Type header.
63
  * Changed: Added compatibility messaging for reCAPTCHA when WooCommerce is active.
@@ -81,4 +89,4 @@ Secure your website with Wordfence Login Security.
81
  * Fix: Fixed a missing icon for some help links when running in standalone mode.
82
 
83
  = 1.0.2 - May 30, 2019 =
84
- * Initial release
3
  Tags: security, login security, 2fa, two factor authentication, captcha, xml-rpc, mfa, 2 factor
4
  Requires at least: 4.5
5
  Requires PHP: 5.3
6
+ Tested up to: 5.7
7
+ Stable tag: 1.0.6
8
 
9
  Secure your website with Wordfence Login Security, providing two-factor authentication, login and registration CAPTCHA, and XML-RPC protection.
10
 
58
 
59
  == Changelog ==
60
 
61
+ = 1.0.6 - January 14, 2021 =
62
+ * Improvement: Made a number of WordPress 5.6 and jQuery 3.x compatibility improvements.
63
+ * Improvement: Replaced the terms whitelist and blacklist with allowlist and blocklist.
64
+ * Fix: Sync roles to new sites in multisite configurations
65
+ * Fix: Corrected 2FA config links in notices for multisite
66
+ * Fix: Corrected inactive user count when users with 2FA have been deleted
67
+ * Fix: reCAPTCHA will no longer block requests with missing tokens in test mode
68
+
69
  = 1.0.5 - January 13, 2020 =
70
  * Changed: AJAX endpoints now send the application/json Content-Type header.
71
  * Changed: Added compatibility messaging for reCAPTCHA when WooCommerce is active.
89
  * Fix: Fixed a missing icon for some help links when running in standalone mode.
90
 
91
  = 1.0.2 - May 30, 2019 =
92
+ * Initial release
views/options/option-ip-source.php CHANGED
@@ -116,7 +116,7 @@ $selectOptions = array(
116
  var option = optionElement.data('option');
117
  var originalValue = optionElement.data('originalValue');
118
 
119
- $(this).attr('checked', originalValue == $(this).attr('value'));
120
  });
121
 
122
  $('#wfls-ip-source-trusted-proxies textarea').each(function() {
116
  var option = optionElement.data('option');
117
  var originalValue = optionElement.data('originalValue');
118
 
119
+ $(this).prop('checked', originalValue == $(this).attr('value'));
120
  });
121
 
122
  $('#wfls-ip-source-trusted-proxies textarea').each(function() {
views/page/manage.php CHANGED
@@ -110,5 +110,5 @@ else if (WORDFENCE_LS_FROM_CORE && $correctedTime != $time) {
110
  echo __('Corrected Time (WF):', 'wordfence-2fa') . ' ' . date('Y-m-d H:i:s', $correctedTime) . ' UTC (' . \WordfenceLS\Controller_Time::format_local_time('Y-m-d H:i:s', $correctedTime) . ' ' . $tz . ')<br>';
111
  }
112
  ?>
113
- <?php _e('Detected IP:', 'wordfence-2fa'); ?> <?php echo \WordfenceLS\Text\Model_HTML::esc_html(\WordfenceLS\Model_Request::current()->ip()); if (\WordfenceLS\Controller_Whitelist::shared()->is_whitelisted(\WordfenceLS\Model_Request::current()->ip())) { echo ' (' . __('whitelisted', 'wordfence-2fa') . ')'; } ?></p>
114
  <?php endif; ?>
110
  echo __('Corrected Time (WF):', 'wordfence-2fa') . ' ' . date('Y-m-d H:i:s', $correctedTime) . ' UTC (' . \WordfenceLS\Controller_Time::format_local_time('Y-m-d H:i:s', $correctedTime) . ' ' . $tz . ')<br>';
111
  }
112
  ?>
113
+ <?php _e('Detected IP:', 'wordfence-2fa'); ?> <?php echo \WordfenceLS\Text\Model_HTML::esc_html(\WordfenceLS\Model_Request::current()->ip()); if (\WordfenceLS\Controller_Whitelist::shared()->is_whitelisted(\WordfenceLS\Model_Request::current()->ip())) { echo ' (' . __('allowlisted', 'wordfence-2fa') . ')'; } ?></p>
114
  <?php endif; ?>
views/page/page.php CHANGED
@@ -5,6 +5,7 @@ if (!defined('WORDFENCE_LS_VERSION')) { exit; }
5
  * @var array $sections The content tabs, each element is an array of the syntax array('tab' => Model_Tab instance, 'title' => Title instance, 'content' => HTML content). Required.
6
  */
7
  ?>
 
8
  <div class="wrap wordfence-ls">
9
  <?php
10
  if (\WordfenceLS\Controller_Permissions::shared()->can_manage_settings() && !\WordfenceLS\Controller_Settings::shared()->get_bool(\WordfenceLS\Controller_Settings::OPTION_DISMISSED_FRESH_INSTALL_MODAL) && !WORDFENCE_LS_FROM_CORE) {
5
  * @var array $sections The content tabs, each element is an array of the syntax array('tab' => Model_Tab instance, 'title' => Title instance, 'content' => HTML content). Required.
6
  */
7
  ?>
8
+ <?php do_action('wfls_activation_page_header'); ?>
9
  <div class="wrap wordfence-ls">
10
  <?php
11
  if (\WordfenceLS\Controller_Permissions::shared()->can_manage_settings() && !\WordfenceLS\Controller_Settings::shared()->get_bool(\WordfenceLS\Controller_Settings::OPTION_DISMISSED_FRESH_INSTALL_MODAL) && !WORDFENCE_LS_FROM_CORE) {
views/settings/options.php CHANGED
@@ -99,9 +99,9 @@ if (!defined('WORDFENCE_LS_VERSION')) { exit; }
99
  echo \WordfenceLS\Model_View::create('options/option-textarea', array(
100
  'textOptionName' => \WordfenceLS\Controller_Settings::OPTION_2FA_WHITELISTED,
101
  'textValue' => implode("\n", \WordfenceLS\Controller_Settings::shared()->whitelisted_ips()),
102
- 'title' => new \WordfenceLS\Text\Model_HTML('<strong>' . __('Whitelisted IP addresses that bypass 2FA', 'wordfence-2fa') . '</strong>'),
103
  'alignTitle' => 'top',
104
- 'subtitle' => __('Whitelisted IPs must be placed on separate lines. You can specify ranges using the following formats: 127.0.0.1/24, 127.0.0.[1-100], or 127.0.0.1-127.0.1.100.', 'wordfence-2fa'),
105
  'subtitlePosition' => 'value',
106
  'noSpacer' => true,
107
  ))->render();
99
  echo \WordfenceLS\Model_View::create('options/option-textarea', array(
100
  'textOptionName' => \WordfenceLS\Controller_Settings::OPTION_2FA_WHITELISTED,
101
  'textValue' => implode("\n", \WordfenceLS\Controller_Settings::shared()->whitelisted_ips()),
102
+ 'title' => new \WordfenceLS\Text\Model_HTML('<strong>' . __('Allowlisted IP addresses that bypass 2FA', 'wordfence-2fa') . '</strong>'),
103
  'alignTitle' => 'top',
104
+ 'subtitle' => __('Allowlisted IPs must be placed on separate lines. You can specify ranges using the following formats: 127.0.0.1/24, 127.0.0.[1-100], or 127.0.0.1-127.0.1.100.', 'wordfence-2fa'),
105
  'subtitlePosition' => 'value',
106
  'noSpacer' => true,
107
  ))->render();
wordfence-login-security.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Wordfence Login Security
4
  Description: Wordfence Login Security
5
  Author: Wordfence
6
  Author URI: http://www.wordfence.com/
7
- Version: 1.0.5
8
  Network: true
9
  */
10
  if (defined('WP_INSTALLING') && WP_INSTALLING) { return; }
@@ -33,8 +33,8 @@ if ($wfCoreActive && !(isset($wfCoreLoading) && $wfCoreLoading)) {
33
  else {
34
  define('WORDFENCE_LS_FROM_CORE', ($wfCoreActive && isset($wfCoreLoading) && $wfCoreLoading));
35
 
36
- define('WORDFENCE_LS_VERSION', '1.0.5');
37
- define('WORDFENCE_LS_BUILD_NUMBER', '1578941826');
38
 
39
  if (!defined('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES')) { define('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES', 15); }
40
 
4
  Description: Wordfence Login Security
5
  Author: Wordfence
6
  Author URI: http://www.wordfence.com/
7
+ Version: 1.0.6
8
  Network: true
9
  */
10
  if (defined('WP_INSTALLING') && WP_INSTALLING) { return; }
33
  else {
34
  define('WORDFENCE_LS_FROM_CORE', ($wfCoreActive && isset($wfCoreLoading) && $wfCoreLoading));
35
 
36
+ define('WORDFENCE_LS_VERSION', '1.0.6');
37
+ define('WORDFENCE_LS_BUILD_NUMBER', '1610634190');
38
 
39
  if (!defined('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES')) { define('WORDFENCE_LS_EMAIL_VALIDITY_DURATION_MINUTES', 15); }
40