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 | Wordfence Login Security |
Version | 1.0.6 |
Comparing to | |
See all releases |
Code changes from version 1.0.5 to 1.0.6
- classes/controller/ajax.php +7 -1
- classes/controller/captcha.php +10 -1
- classes/controller/permissions.php +144 -14
- classes/controller/users.php +1 -1
- classes/controller/wordfencels.php +11 -8
- css/{admin-global.1578941826.css → admin-global.1610634190.css} +0 -0
- css/{admin.1578941826.css → admin.1610634190.css} +0 -0
- css/{colorbox.1578941826.css → colorbox.1610634190.css} +0 -0
- css/{font-awesome.1578941826.css → font-awesome.1610634190.css} +0 -0
- css/{ionicons.1578941826.css → ionicons.1610634190.css} +0 -0
- css/{jquery-ui-timepicker-addon.1578941826.css → jquery-ui-timepicker-addon.1610634190.css} +0 -0
- css/{jquery-ui.min.1578941826.css → jquery-ui.min.1610634190.css} +0 -0
- css/{jquery-ui.structure.min.1578941826.css → jquery-ui.structure.min.1610634190.css} +0 -0
- css/{jquery-ui.theme.min.1578941826.css → jquery-ui.theme.min.1610634190.css} +0 -0
- css/{login.1578941826.css → login.1610634190.css} +0 -0
- css/{wfselect2.min.1578941826.css → wfselect2.min.1610634190.css} +0 -0
- js/{Chart.bundle.min.1578941826.js → Chart.bundle.min.1610634190.js} +0 -0
- js/{admin-global.1578941826.js → admin-global.1610634190.js} +0 -0
- js/{admin.1578941826.js → admin.1610634190.js} +0 -0
- js/{jquery-ui-timepicker-addon.1578941826.js → jquery-ui-timepicker-addon.1610634190.js} +0 -0
- js/{jquery.colorbox.1578941826.js → jquery.colorbox.1610634190.js} +0 -0
- js/{jquery.colorbox.min.1578941826.js → jquery.colorbox.min.1610634190.js} +0 -0
- js/{jquery.qrcode.min.1578941826.js → jquery.qrcode.min.1610634190.js} +0 -0
- js/{jquery.tmpl.min.1578941826.js → jquery.tmpl.min.1610634190.js} +0 -0
- js/{login.1578941826.js → login.1610634190.js} +0 -0
- js/{wfselect2.min.1578941826.js → wfselect2.min.1610634190.js} +0 -0
- readme.txt +11 -3
- views/options/option-ip-source.php +1 -1
- views/page/manage.php +1 -1
- views/page/page.php +1 -0
- views/settings/options.php +2 -2
- 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 |
-
$
|
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 (
|
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 |
-
|
75 |
-
$current_site = $wpdb->set_blog_id($site_id);
|
76 |
$wp_roles = new \WP_Roles();
|
77 |
-
|
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 |
-
|
87 |
$this->_add_cap($role_name, $cap, $wp_roles);
|
88 |
-
|
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 |
-
|
109 |
$this->_remove_cap($role_name, $cap, $wp_roles);
|
110 |
-
|
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:
|
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
|
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
|
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 |
-
|
|
|
|
|
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($
|
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.
|
7 |
-
Stable tag: 1.0.
|
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).
|
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 ' (' . __('
|
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>' . __('
|
103 |
'alignTitle' => 'top',
|
104 |
-
'subtitle' => __('
|
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.
|
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.
|
37 |
-
define('WORDFENCE_LS_BUILD_NUMBER', '
|
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 |
|