Version Description
- Keep legit user from having to repeatedly reset pw during active attacks against their user name.
Download this release
Release Info
Developer | convissor |
Plugin | Login Security Solution |
Version | 0.18.0 |
Comparing to | |
See all releases |
Code changes from version 0.17.0 to 0.18.0
- languages/login-security-solution.pot +74 -40
- login-security-solution.php +126 -9
- readme.txt +11 -5
- tests/LoginFailTest.php +37 -0
- tests/TestCase.php +1 -1
- tests/VerifiedIpTest.php +152 -0
- tests/expected/LoginFailTest--test_wp_login__post_breach_threshold_verified_ip +24 -0
languages/login-security-solution.pot
CHANGED
@@ -2,9 +2,9 @@
|
|
2 |
# This file is distributed under the same license as the Login Security Solution package.
|
3 |
msgid ""
|
4 |
msgstr ""
|
5 |
-
"Project-Id-Version: Login Security Solution 0.
|
6 |
"Report-Msgid-Bugs-To: http://wordpress.org/tag/login-security-solution\n"
|
7 |
-
"POT-Creation-Date: 2012-
|
8 |
"MIME-Version: 1.0\n"
|
9 |
"Content-Type: text/plain; charset=UTF-8\n"
|
10 |
"Content-Transfer-Encoding: 8bit\n"
|
@@ -12,171 +12,205 @@ msgstr ""
|
|
12 |
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
13 |
"Language-Team: LANGUAGE <LL@li.org>\n"
|
14 |
|
15 |
-
#: login-security-solution.php:
|
16 |
msgid "Invalid username or password."
|
17 |
msgstr ""
|
18 |
|
19 |
-
#: login-security-solution.php:
|
20 |
#: tests/LoginErrorsTest.php:129
|
21 |
msgid "Password reset is not allowed for this user"
|
22 |
msgstr ""
|
23 |
|
24 |
-
#: login-security-solution.php:
|
25 |
msgid "It has been over %d minutes since your last action."
|
26 |
msgstr ""
|
27 |
|
28 |
-
#: login-security-solution.php:
|
29 |
msgid "Please log back in."
|
30 |
msgstr ""
|
31 |
|
32 |
-
#: login-security-solution.php:
|
33 |
msgid "The grace period for changing your password has expired."
|
34 |
msgstr ""
|
35 |
|
36 |
-
#: login-security-solution.php:
|
37 |
msgid "Please submit this form to reset your password."
|
38 |
msgstr ""
|
39 |
|
40 |
-
#: login-security-solution.php:
|
41 |
msgid "Your password must be reset."
|
42 |
msgstr ""
|
43 |
|
44 |
-
#: login-security-solution.php:
|
45 |
msgid "Please submit this form to reset it."
|
46 |
msgstr ""
|
47 |
|
48 |
-
#: login-security-solution.php:
|
49 |
msgid "Your password has expired. Please log and change it."
|
50 |
msgstr ""
|
51 |
|
52 |
-
#: login-security-solution.php:
|
53 |
msgid "We provide a %d minute grace period to do so."
|
54 |
msgstr ""
|
55 |
|
56 |
-
#: login-security-solution.php:
|
57 |
msgid "The password you tried to create is not secure. Please try again."
|
58 |
msgstr ""
|
59 |
|
60 |
-
#: login-security-solution.php:
|
61 |
#: tests/LoginMessageTest.php:144
|
62 |
msgid "The site is undergoing maintenance."
|
63 |
msgstr ""
|
64 |
|
65 |
-
#: login-security-solution.php:
|
66 |
#: tests/LoginMessageTest.php:145
|
67 |
msgid "Please try again later."
|
68 |
msgstr ""
|
69 |
|
70 |
-
#: login-security-solution.php:
|
71 |
msgid "Passwords can not be reused."
|
72 |
msgstr ""
|
73 |
|
74 |
-
#: login-security-solution.php:
|
75 |
msgid "ERROR"
|
76 |
msgstr ""
|
77 |
|
78 |
-
#: login-security-solution.php:
|
79 |
msgid "Component Count Value from Current Attempt"
|
80 |
msgstr ""
|
81 |
|
82 |
-
#: login-security-solution.php:
|
83 |
msgid "Network IP %5d %s"
|
84 |
msgstr ""
|
85 |
|
86 |
-
#: login-security-solution.php:
|
87 |
msgid "Username %5d %s"
|
88 |
msgstr ""
|
89 |
|
90 |
-
#: login-security-solution.php:
|
91 |
msgid "Password MD5 %5d %s"
|
92 |
msgstr ""
|
93 |
|
94 |
-
#: login-security-solution.php:
|
|
|
|
|
|
|
|
|
95 |
msgid "Your website, %s, may have been broken in to."
|
96 |
msgstr ""
|
97 |
|
98 |
-
#: login-security-solution.php:
|
99 |
msgid ""
|
100 |
"Someone just logged in using the following components. Prior to that, some "
|
101 |
"combination of those components were a part of %d failed attempts to log in "
|
102 |
"during the past %d minutes:"
|
103 |
msgstr ""
|
104 |
|
105 |
-
#: login-security-solution.php:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
msgid ""
|
107 |
"The user has been logged out and will be required to confirm their identity "
|
108 |
"via the password reset functionality."
|
109 |
msgstr ""
|
110 |
|
111 |
-
#: login-security-solution.php:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
msgid "Your website, %s, is undergoing a brute force attack."
|
113 |
msgstr ""
|
114 |
|
115 |
-
#: login-security-solution.php:
|
116 |
msgid ""
|
117 |
"There have been at least %d failed attempts to log in during the past %d "
|
118 |
"minutes that used one or more of the following components:"
|
119 |
msgstr ""
|
120 |
|
121 |
-
#: login-security-solution.php:
|
122 |
msgid ""
|
123 |
"The %s plugin for WordPress is repelling the attack by making their login "
|
124 |
"failures take a very long time."
|
125 |
msgstr ""
|
126 |
|
127 |
-
#: login-security-solution.php:
|
128 |
msgid "Password not set."
|
129 |
msgstr ""
|
130 |
|
131 |
-
#: login-security-solution.php:
|
132 |
msgid "Passwords must be strings."
|
133 |
msgstr ""
|
134 |
|
135 |
-
#: login-security-solution.php:
|
136 |
msgid "Passwords must use ASCII characters."
|
137 |
msgstr ""
|
138 |
|
139 |
-
#: login-security-solution.php:
|
140 |
msgid "Password is too short."
|
141 |
msgstr ""
|
142 |
|
143 |
-
#: login-security-solution.php:
|
144 |
msgid "Passwords must either contain numbers or be %d characters long."
|
145 |
msgstr ""
|
146 |
|
147 |
-
#: login-security-solution.php:
|
148 |
msgid ""
|
149 |
"Passwords must either contain punctuation marks / symbols or be %d "
|
150 |
"characters long."
|
151 |
msgstr ""
|
152 |
|
153 |
-
#: login-security-solution.php:
|
154 |
msgid ""
|
155 |
"Passwords must either contain upper-case and lower-case letters or be %d "
|
156 |
"characters long."
|
157 |
msgstr ""
|
158 |
|
159 |
-
#: login-security-solution.php:
|
160 |
msgid "Passwords can't be sequential keys."
|
161 |
msgstr ""
|
162 |
|
163 |
-
#: login-security-solution.php:
|
164 |
msgid "Passwords can't have that many sequential characters."
|
165 |
msgstr ""
|
166 |
|
167 |
-
#: login-security-solution.php:
|
168 |
msgid "Passwords can't contain user data."
|
169 |
msgstr ""
|
170 |
|
171 |
-
#: login-security-solution.php:
|
172 |
msgid "Passwords can't contain site info."
|
173 |
msgstr ""
|
174 |
|
175 |
-
#: login-security-solution.php:
|
176 |
msgid "Password is too common."
|
177 |
msgstr ""
|
178 |
|
179 |
-
#: login-security-solution.php:
|
180 |
msgid "Passwords can't be variations of dictionary words."
|
181 |
msgstr ""
|
182 |
|
2 |
# This file is distributed under the same license as the Login Security Solution package.
|
3 |
msgid ""
|
4 |
msgstr ""
|
5 |
+
"Project-Id-Version: Login Security Solution 0.18.0\n"
|
6 |
"Report-Msgid-Bugs-To: http://wordpress.org/tag/login-security-solution\n"
|
7 |
+
"POT-Creation-Date: 2012-07-11 16:41:18+00:00\n"
|
8 |
"MIME-Version: 1.0\n"
|
9 |
"Content-Type: text/plain; charset=UTF-8\n"
|
10 |
"Content-Transfer-Encoding: 8bit\n"
|
12 |
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
13 |
"Language-Team: LANGUAGE <LL@li.org>\n"
|
14 |
|
15 |
+
#: login-security-solution.php:495
|
16 |
msgid "Invalid username or password."
|
17 |
msgstr ""
|
18 |
|
19 |
+
#: login-security-solution.php:501 tests/LoginErrorsTest.php:117
|
20 |
#: tests/LoginErrorsTest.php:129
|
21 |
msgid "Password reset is not allowed for this user"
|
22 |
msgstr ""
|
23 |
|
24 |
+
#: login-security-solution.php:526 tests/LoginMessageTest.php:66
|
25 |
msgid "It has been over %d minutes since your last action."
|
26 |
msgstr ""
|
27 |
|
28 |
+
#: login-security-solution.php:527 tests/LoginMessageTest.php:67
|
29 |
msgid "Please log back in."
|
30 |
msgstr ""
|
31 |
|
32 |
+
#: login-security-solution.php:530 tests/LoginMessageTest.php:77
|
33 |
msgid "The grace period for changing your password has expired."
|
34 |
msgstr ""
|
35 |
|
36 |
+
#: login-security-solution.php:531 tests/LoginMessageTest.php:78
|
37 |
msgid "Please submit this form to reset your password."
|
38 |
msgstr ""
|
39 |
|
40 |
+
#: login-security-solution.php:534 tests/LoginMessageTest.php:88
|
41 |
msgid "Your password must be reset."
|
42 |
msgstr ""
|
43 |
|
44 |
+
#: login-security-solution.php:535 tests/LoginMessageTest.php:89
|
45 |
msgid "Please submit this form to reset it."
|
46 |
msgstr ""
|
47 |
|
48 |
+
#: login-security-solution.php:538 tests/LoginMessageTest.php:104
|
49 |
msgid "Your password has expired. Please log and change it."
|
50 |
msgstr ""
|
51 |
|
52 |
+
#: login-security-solution.php:539 tests/LoginMessageTest.php:105
|
53 |
msgid "We provide a %d minute grace period to do so."
|
54 |
msgstr ""
|
55 |
|
56 |
+
#: login-security-solution.php:542 tests/LoginMessageTest.php:115
|
57 |
msgid "The password you tried to create is not secure. Please try again."
|
58 |
msgstr ""
|
59 |
|
60 |
+
#: login-security-solution.php:548 tests/LoginMessageTest.php:129
|
61 |
#: tests/LoginMessageTest.php:144
|
62 |
msgid "The site is undergoing maintenance."
|
63 |
msgstr ""
|
64 |
|
65 |
+
#: login-security-solution.php:549 tests/LoginMessageTest.php:130
|
66 |
#: tests/LoginMessageTest.php:145
|
67 |
msgid "Please try again later."
|
68 |
msgstr ""
|
69 |
|
70 |
+
#: login-security-solution.php:619
|
71 |
msgid "Passwords can not be reused."
|
72 |
msgstr ""
|
73 |
|
74 |
+
#: login-security-solution.php:794
|
75 |
msgid "ERROR"
|
76 |
msgstr ""
|
77 |
|
78 |
+
#: login-security-solution.php:919
|
79 |
msgid "Component Count Value from Current Attempt"
|
80 |
msgstr ""
|
81 |
|
82 |
+
#: login-security-solution.php:921
|
83 |
msgid "Network IP %5d %s"
|
84 |
msgstr ""
|
85 |
|
86 |
+
#: login-security-solution.php:923
|
87 |
msgid "Username %5d %s"
|
88 |
msgstr ""
|
89 |
|
90 |
+
#: login-security-solution.php:925
|
91 |
msgid "Password MD5 %5d %s"
|
92 |
msgstr ""
|
93 |
|
94 |
+
#: login-security-solution.php:1722 login-security-solution.php:1759
|
95 |
+
msgid "POTENTIAL INTRUSION AT %s"
|
96 |
+
msgstr ""
|
97 |
+
|
98 |
+
#: login-security-solution.php:1726
|
99 |
msgid "Your website, %s, may have been broken in to."
|
100 |
msgstr ""
|
101 |
|
102 |
+
#: login-security-solution.php:1729
|
103 |
msgid ""
|
104 |
"Someone just logged in using the following components. Prior to that, some "
|
105 |
"combination of those components were a part of %d failed attempts to log in "
|
106 |
"during the past %d minutes:"
|
107 |
msgstr ""
|
108 |
|
109 |
+
#: login-security-solution.php:1735
|
110 |
+
msgid ""
|
111 |
+
"The user's current IP address is one they have verified with your site in "
|
112 |
+
"the past. Therefore, the user will NOT be required to confirm their "
|
113 |
+
"identity via the password reset process. An email will be sent to them, "
|
114 |
+
"just in case this actually was a breach."
|
115 |
+
msgstr ""
|
116 |
+
|
117 |
+
#: login-security-solution.php:1737
|
118 |
msgid ""
|
119 |
"The user has been logged out and will be required to confirm their identity "
|
120 |
"via the password reset functionality."
|
121 |
msgstr ""
|
122 |
|
123 |
+
#: login-security-solution.php:1763
|
124 |
+
msgid ""
|
125 |
+
"Someone just logged into your '%s' account at %s. Was it you that logged "
|
126 |
+
"in? We are asking because the site is being attacked."
|
127 |
+
msgstr ""
|
128 |
+
|
129 |
+
#: login-security-solution.php:1764
|
130 |
+
msgid "IF IT WAS NOT YOU, please do the following right away:"
|
131 |
+
msgstr ""
|
132 |
+
|
133 |
+
#: login-security-solution.php:1765
|
134 |
+
msgid "1) Log into %s and change your password."
|
135 |
+
msgstr ""
|
136 |
+
|
137 |
+
#: login-security-solution.php:1766
|
138 |
+
msgid "2) Send an email to %s letting them know it was not you who logged in."
|
139 |
+
msgstr ""
|
140 |
+
|
141 |
+
#: login-security-solution.php:1792
|
142 |
+
msgid "ATTACK HAPPENING TO %s"
|
143 |
+
msgstr ""
|
144 |
+
|
145 |
+
#: login-security-solution.php:1796
|
146 |
msgid "Your website, %s, is undergoing a brute force attack."
|
147 |
msgstr ""
|
148 |
|
149 |
+
#: login-security-solution.php:1799
|
150 |
msgid ""
|
151 |
"There have been at least %d failed attempts to log in during the past %d "
|
152 |
"minutes that used one or more of the following components:"
|
153 |
msgstr ""
|
154 |
|
155 |
+
#: login-security-solution.php:1804
|
156 |
msgid ""
|
157 |
"The %s plugin for WordPress is repelling the attack by making their login "
|
158 |
"failures take a very long time."
|
159 |
msgstr ""
|
160 |
|
161 |
+
#: login-security-solution.php:2155
|
162 |
msgid "Password not set."
|
163 |
msgstr ""
|
164 |
|
165 |
+
#: login-security-solution.php:2170
|
166 |
msgid "Passwords must be strings."
|
167 |
msgstr ""
|
168 |
|
169 |
+
#: login-security-solution.php:2188
|
170 |
msgid "Passwords must use ASCII characters."
|
171 |
msgstr ""
|
172 |
|
173 |
+
#: login-security-solution.php:2207
|
174 |
msgid "Password is too short."
|
175 |
msgstr ""
|
176 |
|
177 |
+
#: login-security-solution.php:2216
|
178 |
msgid "Passwords must either contain numbers or be %d characters long."
|
179 |
msgstr ""
|
180 |
|
181 |
+
#: login-security-solution.php:2225
|
182 |
msgid ""
|
183 |
"Passwords must either contain punctuation marks / symbols or be %d "
|
184 |
"characters long."
|
185 |
msgstr ""
|
186 |
|
187 |
+
#: login-security-solution.php:2234
|
188 |
msgid ""
|
189 |
"Passwords must either contain upper-case and lower-case letters or be %d "
|
190 |
"characters long."
|
191 |
msgstr ""
|
192 |
|
193 |
+
#: login-security-solution.php:2244
|
194 |
msgid "Passwords can't be sequential keys."
|
195 |
msgstr ""
|
196 |
|
197 |
+
#: login-security-solution.php:2253
|
198 |
msgid "Passwords can't have that many sequential characters."
|
199 |
msgstr ""
|
200 |
|
201 |
+
#: login-security-solution.php:2269
|
202 |
msgid "Passwords can't contain user data."
|
203 |
msgstr ""
|
204 |
|
205 |
+
#: login-security-solution.php:2280
|
206 |
msgid "Passwords can't contain site info."
|
207 |
msgstr ""
|
208 |
|
209 |
+
#: login-security-solution.php:2289
|
210 |
msgid "Password is too common."
|
211 |
msgstr ""
|
212 |
|
213 |
+
#: login-security-solution.php:2298
|
214 |
msgid "Passwords can't be variations of dictionary words."
|
215 |
msgstr ""
|
216 |
|
login-security-solution.php
CHANGED
@@ -6,7 +6,7 @@
|
|
6 |
* Description: Requires very strong passwords, repels brute force login attacks, prevents login information disclosures, expires idle sessions, notifies admins of attacks and breaches, permits administrators to disable logins for maintenance or emergency reasons and reset all passwords.
|
7 |
*
|
8 |
* Plugin URI: http://wordpress.org/extend/plugins/login-security-solution/
|
9 |
-
* Version: 0.
|
10 |
* Author: Daniel Convissor
|
11 |
* Author URI: http://www.analysisandsolutions.com/
|
12 |
* License: GPLv2
|
@@ -164,6 +164,12 @@ class login_security_solution {
|
|
164 |
*/
|
165 |
protected $umk_pw_force_change;
|
166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
|
168 |
/**
|
169 |
* Declares the WordPress action and filter callbacks
|
@@ -264,6 +270,7 @@ class login_security_solution {
|
|
264 |
$this->umk_grace_period = self::ID . '-pw-grace-period-start-time';
|
265 |
$this->umk_hashes = self::ID . '-pw-hashes';
|
266 |
$this->umk_last_active = self::ID . '-last-active';
|
|
|
267 |
|
268 |
$this->dir_dictionaries = dirname(__FILE__) . '/pw_dictionaries/';
|
269 |
$this->dir_sequences = dirname(__FILE__) . '/pw_sequences/';
|
@@ -577,6 +584,7 @@ class login_security_solution {
|
|
577 |
return -1;
|
578 |
}
|
579 |
|
|
|
580 |
$this->process_pw_metadata($user->ID, $user_pass);
|
581 |
}
|
582 |
|
@@ -619,6 +627,9 @@ class login_security_solution {
|
|
619 |
// Empty ID means an admin is adding a new user.
|
620 |
if (!empty($user->ID) && !$errors->get_error_codes()) {
|
621 |
$this->process_pw_metadata($user->ID, $user->user_pass);
|
|
|
|
|
|
|
622 |
}
|
623 |
|
624 |
return $answer;
|
@@ -634,11 +645,14 @@ class login_security_solution {
|
|
634 |
* @param WP_User $user the current user
|
635 |
* @return mixed return values provided for unit testing
|
636 |
*
|
|
|
|
|
637 |
* @uses login_security_solution::get_network_ip() gets the IP's
|
638 |
* "network" part
|
639 |
* @uses login_security_solution::md5() to hash the password
|
640 |
* @uses login_security_solution::get_login_fail() to see if
|
641 |
* they're over the limit
|
|
|
642 |
* @uses login_security_solution::$options for the
|
643 |
* login_fail_breach_notify value
|
644 |
* @uses login_security_solution::$options for the
|
@@ -657,14 +671,31 @@ class login_security_solution {
|
|
657 |
return -1;
|
658 |
}
|
659 |
|
660 |
-
$
|
|
|
661 |
$pass_md5 = $this->md5(empty($_POST['pwd']) ? '' : $_POST['pwd']);
|
662 |
|
663 |
$return = 1;
|
664 |
$fails = $this->get_login_fail($network_ip, $user_name, $pass_md5);
|
665 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
666 |
if ($this->options['login_fail_breach_pw_force_change']
|
667 |
-
&& $fails['total'] >= $this->options['login_fail_breach_pw_force_change']
|
|
|
668 |
{
|
669 |
###$this->log("wp_login(): Breach force change.");
|
670 |
$this->set_pw_force_change($user->ID);
|
@@ -674,8 +705,13 @@ class login_security_solution {
|
|
674 |
if ($this->options['login_fail_breach_notify']
|
675 |
&& $fails['total'] >= $this->options['login_fail_breach_notify'])
|
676 |
{
|
|
|
677 |
###$this->log("wp_login(): Breach notify.");
|
678 |
-
$this->notify_breach($network_ip, $user_name, $pass_md5, $fails
|
|
|
|
|
|
|
|
|
679 |
$return += 4;
|
680 |
}
|
681 |
|
@@ -910,6 +946,22 @@ $this->log($sql);
|
|
910 |
return (bool) get_user_meta($user_ID, $this->umk_pw_force_change, true);
|
911 |
}
|
912 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
913 |
/**
|
914 |
* Obtains the timestamp of when the user's "password grace period"
|
915 |
* started
|
@@ -1653,20 +1705,21 @@ $this->log($sql);
|
|
1653 |
* @param string $user_name the user name from the current login form
|
1654 |
* @param string $pass_md5 the md5 hashed new password
|
1655 |
* @param array $fails the data from get_login_fail()
|
|
|
1656 |
* @return bool
|
1657 |
*
|
1658 |
* @uses login_security_solution::get_notify_counts() for some shared text
|
1659 |
* @uses wp_mail() to send the messages
|
1660 |
*/
|
1661 |
protected function notify_breach($network_ip, $user_name, $pass_md5,
|
1662 |
-
$fails)
|
1663 |
{
|
1664 |
$this->load_plugin_textdomain();
|
1665 |
|
1666 |
$to = $this->sanitize_whitespace(get_option('admin_email'));
|
1667 |
|
1668 |
$blog = get_option('blogname');
|
1669 |
-
$subject = sprintf(
|
1670 |
$subject = $this->sanitize_whitespace($subject);
|
1671 |
|
1672 |
$message =
|
@@ -1676,9 +1729,41 @@ $this->log($sql);
|
|
1676 |
. sprintf(__("Someone just logged in using the following components. Prior to that, some combination of those components were a part of %d failed attempts to log in during the past %d minutes:", self::ID),
|
1677 |
$fails['total'], $this->options['login_fail_minutes']) . "\n\n"
|
1678 |
|
1679 |
-
. $this->get_notify_counts($network_ip, $user_name, $pass_md5, $fails)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1680 |
|
1681 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1682 |
|
1683 |
return wp_mail($to, $subject, $message);
|
1684 |
}
|
@@ -1704,7 +1789,7 @@ $this->log($sql);
|
|
1704 |
$to = $this->sanitize_whitespace(get_option('admin_email'));
|
1705 |
|
1706 |
$blog = get_option('blogname');
|
1707 |
-
$subject = sprintf(
|
1708 |
$subject = $this->sanitize_whitespace($subject);
|
1709 |
|
1710 |
$message =
|
@@ -1896,6 +1981,38 @@ $this->log($sql);
|
|
1896 |
return true;
|
1897 |
}
|
1898 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1899 |
/**
|
1900 |
* Stores the present time in the given user's "last active" metadata
|
1901 |
*
|
6 |
* Description: Requires very strong passwords, repels brute force login attacks, prevents login information disclosures, expires idle sessions, notifies admins of attacks and breaches, permits administrators to disable logins for maintenance or emergency reasons and reset all passwords.
|
7 |
*
|
8 |
* Plugin URI: http://wordpress.org/extend/plugins/login-security-solution/
|
9 |
+
* Version: 0.18.0
|
10 |
* Author: Daniel Convissor
|
11 |
* Author URI: http://www.analysisandsolutions.com/
|
12 |
* License: GPLv2
|
164 |
*/
|
165 |
protected $umk_pw_force_change;
|
166 |
|
167 |
+
/**
|
168 |
+
* Our usermeta key for tracking this user's verified IP addresses
|
169 |
+
* @var string
|
170 |
+
*/
|
171 |
+
protected $umk_verified_ips;
|
172 |
+
|
173 |
|
174 |
/**
|
175 |
* Declares the WordPress action and filter callbacks
|
270 |
$this->umk_grace_period = self::ID . '-pw-grace-period-start-time';
|
271 |
$this->umk_hashes = self::ID . '-pw-hashes';
|
272 |
$this->umk_last_active = self::ID . '-last-active';
|
273 |
+
$this->umk_verified_ips = self::ID . '-verified-ips';
|
274 |
|
275 |
$this->dir_dictionaries = dirname(__FILE__) . '/pw_dictionaries/';
|
276 |
$this->dir_sequences = dirname(__FILE__) . '/pw_sequences/';
|
584 |
return -1;
|
585 |
}
|
586 |
|
587 |
+
$this->save_verified_ip($user->ID, $this->get_ip());
|
588 |
$this->process_pw_metadata($user->ID, $user_pass);
|
589 |
}
|
590 |
|
627 |
// Empty ID means an admin is adding a new user.
|
628 |
if (!empty($user->ID) && !$errors->get_error_codes()) {
|
629 |
$this->process_pw_metadata($user->ID, $user->user_pass);
|
630 |
+
if ($user->ID == get_current_user_id()) {
|
631 |
+
$this->save_verified_ip($user->ID, $this->get_ip());
|
632 |
+
}
|
633 |
}
|
634 |
|
635 |
return $answer;
|
645 |
* @param WP_User $user the current user
|
646 |
* @return mixed return values provided for unit testing
|
647 |
*
|
648 |
+
* @uses login_security_solution::get_ip() to get the
|
649 |
+
* $_SERVER['REMOTE_ADDR']
|
650 |
* @uses login_security_solution::get_network_ip() gets the IP's
|
651 |
* "network" part
|
652 |
* @uses login_security_solution::md5() to hash the password
|
653 |
* @uses login_security_solution::get_login_fail() to see if
|
654 |
* they're over the limit
|
655 |
+
* @uses login_security_solution::get_verified_ips() to check legitimacy
|
656 |
* @uses login_security_solution::$options for the
|
657 |
* login_fail_breach_notify value
|
658 |
* @uses login_security_solution::$options for the
|
671 |
return -1;
|
672 |
}
|
673 |
|
674 |
+
$ip = $this->get_ip();
|
675 |
+
$network_ip = $this->get_network_ip($ip);
|
676 |
$pass_md5 = $this->md5(empty($_POST['pwd']) ? '' : $_POST['pwd']);
|
677 |
|
678 |
$return = 1;
|
679 |
$fails = $this->get_login_fail($network_ip, $user_name, $pass_md5);
|
680 |
|
681 |
+
/*
|
682 |
+
* Keep legitimate users from having to repeatedly reset passwords
|
683 |
+
* during active attacks against their user name (password ). Do this
|
684 |
+
* if the user's current IP address is not involved with any of the
|
685 |
+
* recent failed logins and the current IP address has been verified.
|
686 |
+
*/
|
687 |
+
if (!$fails['network_ip']
|
688 |
+
&& in_array($ip, $this->get_verified_ips($user->ID)))
|
689 |
+
{
|
690 |
+
$return += 8;
|
691 |
+
$verified_ip = true;
|
692 |
+
} else {
|
693 |
+
$verified_ip = false;
|
694 |
+
}
|
695 |
+
|
696 |
if ($this->options['login_fail_breach_pw_force_change']
|
697 |
+
&& $fails['total'] >= $this->options['login_fail_breach_pw_force_change']
|
698 |
+
&& !$verified_ip)
|
699 |
{
|
700 |
###$this->log("wp_login(): Breach force change.");
|
701 |
$this->set_pw_force_change($user->ID);
|
705 |
if ($this->options['login_fail_breach_notify']
|
706 |
&& $fails['total'] >= $this->options['login_fail_breach_notify'])
|
707 |
{
|
708 |
+
// Send this, even if IP is verified, just in case.
|
709 |
###$this->log("wp_login(): Breach notify.");
|
710 |
+
$this->notify_breach($network_ip, $user_name, $pass_md5, $fails,
|
711 |
+
$verified_ip);
|
712 |
+
if ($verified_ip) {
|
713 |
+
$this->notify_breach_user($user);
|
714 |
+
}
|
715 |
$return += 4;
|
716 |
}
|
717 |
|
946 |
return (bool) get_user_meta($user_ID, $this->umk_pw_force_change, true);
|
947 |
}
|
948 |
|
949 |
+
/**
|
950 |
+
* Lists IP addresses known to be good for the user
|
951 |
+
*
|
952 |
+
* @param int $user_ID the current user's ID number
|
953 |
+
* @return array the IP addresses
|
954 |
+
*/
|
955 |
+
protected function get_verified_ips($user_ID) {
|
956 |
+
$out = get_user_meta($user_ID, $this->umk_verified_ips, true);
|
957 |
+
if (empty($out)) {
|
958 |
+
$out = array();
|
959 |
+
} elseif (!is_array($out)) {
|
960 |
+
$out = (array) $out;
|
961 |
+
}
|
962 |
+
return $out;
|
963 |
+
}
|
964 |
+
|
965 |
/**
|
966 |
* Obtains the timestamp of when the user's "password grace period"
|
967 |
* started
|
1705 |
* @param string $user_name the user name from the current login form
|
1706 |
* @param string $pass_md5 the md5 hashed new password
|
1707 |
* @param array $fails the data from get_login_fail()
|
1708 |
+
* @param bool $verified_ip is the user coming form a verified ip?
|
1709 |
* @return bool
|
1710 |
*
|
1711 |
* @uses login_security_solution::get_notify_counts() for some shared text
|
1712 |
* @uses wp_mail() to send the messages
|
1713 |
*/
|
1714 |
protected function notify_breach($network_ip, $user_name, $pass_md5,
|
1715 |
+
$fails, $verified_ip)
|
1716 |
{
|
1717 |
$this->load_plugin_textdomain();
|
1718 |
|
1719 |
$to = $this->sanitize_whitespace(get_option('admin_email'));
|
1720 |
|
1721 |
$blog = get_option('blogname');
|
1722 |
+
$subject = sprintf(__("POTENTIAL INTRUSION AT %s", self::ID), $blog);
|
1723 |
$subject = $this->sanitize_whitespace($subject);
|
1724 |
|
1725 |
$message =
|
1729 |
. sprintf(__("Someone just logged in using the following components. Prior to that, some combination of those components were a part of %d failed attempts to log in during the past %d minutes:", self::ID),
|
1730 |
$fails['total'], $this->options['login_fail_minutes']) . "\n\n"
|
1731 |
|
1732 |
+
. $this->get_notify_counts($network_ip, $user_name, $pass_md5, $fails);
|
1733 |
+
|
1734 |
+
if ($verified_ip) {
|
1735 |
+
$message .= __("The user's current IP address is one they have verified with your site in the past. Therefore, the user will NOT be required to confirm their identity via the password reset process. An email will be sent to them, just in case this actually was a breach.", self::ID) . "\n";
|
1736 |
+
} else {
|
1737 |
+
$message .= __("The user has been logged out and will be required to confirm their identity via the password reset functionality.", self::ID) . "\n";
|
1738 |
+
}
|
1739 |
+
|
1740 |
+
return wp_mail($to, $subject, $message);
|
1741 |
+
}
|
1742 |
+
|
1743 |
+
/**
|
1744 |
+
* Sends an email to the current user letting them know a breakin
|
1745 |
+
* may have occurred
|
1746 |
+
*
|
1747 |
+
* @param WP_User $user the current user
|
1748 |
+
* @return bool
|
1749 |
+
*
|
1750 |
+
* @uses wp_mail() to send the messages
|
1751 |
+
*/
|
1752 |
+
protected function notify_breach_user($user)
|
1753 |
+
{
|
1754 |
+
$this->load_plugin_textdomain();
|
1755 |
|
1756 |
+
$to = $this->sanitize_whitespace($user->user_email);
|
1757 |
+
|
1758 |
+
$blog = get_option('blogname');
|
1759 |
+
$subject = sprintf(__("POTENTIAL INTRUSION AT %s", self::ID), $blog);
|
1760 |
+
$subject = $this->sanitize_whitespace($subject);
|
1761 |
+
|
1762 |
+
$message =
|
1763 |
+
sprintf(__("Someone just logged into your '%s' account at %s. Was it you that logged in? We are asking because the site is being attacked.", self::ID), $user->user_login, get_option('siteurl')) . "\n\n"
|
1764 |
+
. __("IF IT WAS NOT YOU, please do the following right away:", self::ID) . "\n\n"
|
1765 |
+
. sprintf(__("1) Log into %s and change your password.", self::ID), wp_login_url()) . "\n\n"
|
1766 |
+
. sprintf(__("2) Send an email to %s letting them know it was not you who logged in.", self::ID), get_option('admin_email')) . "\n";
|
1767 |
|
1768 |
return wp_mail($to, $subject, $message);
|
1769 |
}
|
1789 |
$to = $this->sanitize_whitespace(get_option('admin_email'));
|
1790 |
|
1791 |
$blog = get_option('blogname');
|
1792 |
+
$subject = sprintf(__("ATTACK HAPPENING TO %s", self::ID), $blog);
|
1793 |
$subject = $this->sanitize_whitespace($subject);
|
1794 |
|
1795 |
$message =
|
1981 |
return true;
|
1982 |
}
|
1983 |
|
1984 |
+
/**
|
1985 |
+
* Stores the user's current IP address
|
1986 |
+
*
|
1987 |
+
* Note: saves up to 10 adddresses, duplicates are not stored.
|
1988 |
+
*
|
1989 |
+
* @param int $user_ID the user's id number
|
1990 |
+
* @param string $new_ip the ip address to add
|
1991 |
+
* @return mixed true on success, 1 if IP is already stored, -1 if IP empty
|
1992 |
+
*/
|
1993 |
+
protected function save_verified_ip($user_ID, $new_ip) {
|
1994 |
+
if (!$new_ip) {
|
1995 |
+
return -1;
|
1996 |
+
}
|
1997 |
+
|
1998 |
+
$ips = $this->get_verified_ips($user_ID);
|
1999 |
+
|
2000 |
+
if (in_array($new_ip, $ips)) {
|
2001 |
+
return 1;
|
2002 |
+
}
|
2003 |
+
|
2004 |
+
$ips[] = $new_ip;
|
2005 |
+
|
2006 |
+
$cut = count($ips) - 10;
|
2007 |
+
if ($cut > 0) {
|
2008 |
+
array_splice($ips, 0, $cut);
|
2009 |
+
}
|
2010 |
+
|
2011 |
+
update_user_meta($user_ID, $this->umk_verified_ips, $ips);
|
2012 |
+
|
2013 |
+
return true;
|
2014 |
+
}
|
2015 |
+
|
2016 |
/**
|
2017 |
* Stores the present time in the given user's "last active" metadata
|
2018 |
*
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=danie
|
|
4 |
Tags: login, password, passwords, strength, strong, strong passwords, password strength, idle, timeout, maintenance, security, attack, hack, lock, ban, brute force, brute, force, authentication, auth, cookie, users
|
5 |
Requires at least: 3.3
|
6 |
Tested up to: 3.4.1
|
7 |
-
Stable tag: 0.
|
8 |
|
9 |
Security against brute force attacks by tracking IP, name, password; requiring very strong passwords. Idle timeout. Maintenance mode. Multisite ready!
|
10 |
|
@@ -23,9 +23,11 @@ legitimate users or administrators
|
|
23 |
This limits attackers ability to effectively probe your site,
|
24 |
so they'll give up and go find an easier target.
|
25 |
+ If an account seems breached, the "user" is immediately logged out
|
26 |
-
and forced to use WordPress' password reset utility.
|
27 |
-
any damage from being done and verifies the user's identity.
|
28 |
-
|
|
|
|
|
29 |
+ Can notify the administrator of attacks and breaches
|
30 |
+ Supports IPv6
|
31 |
|
@@ -268,13 +270,17 @@ plugin's login failure process.
|
|
268 |
Get the translation tools from `http://i18n.svn.wordpress.org/tools/trunk/`
|
269 |
then `cd` into that directory and run:
|
270 |
|
271 |
-
php
|
272 |
../login-security-solution \
|
273 |
../login-security-solution/languages/login-security-solution.pot
|
274 |
|
275 |
|
276 |
== Changelog ==
|
277 |
|
|
|
|
|
|
|
|
|
278 |
= 0.17.0 =
|
279 |
* Fix network IP query in get_login_fail(). (Bug #1553, deanmarktaylor)
|
280 |
* Rename files holding expected test results. (Bug #1552, deanmarktaylor)
|
4 |
Tags: login, password, passwords, strength, strong, strong passwords, password strength, idle, timeout, maintenance, security, attack, hack, lock, ban, brute force, brute, force, authentication, auth, cookie, users
|
5 |
Requires at least: 3.3
|
6 |
Tested up to: 3.4.1
|
7 |
+
Stable tag: 0.18.0
|
8 |
|
9 |
Security against brute force attacks by tracking IP, name, password; requiring very strong passwords. Idle timeout. Maintenance mode. Multisite ready!
|
10 |
|
23 |
This limits attackers ability to effectively probe your site,
|
24 |
so they'll give up and go find an easier target.
|
25 |
+ If an account seems breached, the "user" is immediately logged out
|
26 |
+
and forced to use WordPress' password reset utility. This prevents
|
27 |
+
any damage from being done and verifies the user's identity. But
|
28 |
+
if the user is coming in from an IP address they have used in the
|
29 |
+
past, an email is sent to the user making sure it was them logging in.
|
30 |
+
All without intervention by an administrator.
|
31 |
+ Can notify the administrator of attacks and breaches
|
32 |
+ Supports IPv6
|
33 |
|
270 |
Get the translation tools from `http://i18n.svn.wordpress.org/tools/trunk/`
|
271 |
then `cd` into that directory and run:
|
272 |
|
273 |
+
php -d 'error_reporting=E_ALL^E_STRICT' makepot.php wp-plugin \
|
274 |
../login-security-solution \
|
275 |
../login-security-solution/languages/login-security-solution.pot
|
276 |
|
277 |
|
278 |
== Changelog ==
|
279 |
|
280 |
+
= 0.18.0 =
|
281 |
+
* Keep legit user from having to repeatedly reset pw during active attacks
|
282 |
+
against their user name.
|
283 |
+
|
284 |
= 0.17.0 =
|
285 |
* Fix network IP query in get_login_fail(). (Bug #1553, deanmarktaylor)
|
286 |
* Rename files holding expected test results. (Bug #1552, deanmarktaylor)
|
tests/LoginFailTest.php
CHANGED
@@ -195,6 +195,39 @@ class LoginFailTest extends TestCase {
|
|
195 |
$actual = self::$lss->get_pw_force_change($this->user->ID);
|
196 |
$this->assertTrue($actual, 'get_pw_force_change() return value...');
|
197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
$this->check_mail_file();
|
199 |
}
|
200 |
|
@@ -231,6 +264,10 @@ class LoginFailTest extends TestCase {
|
|
231 |
$options['login_fail_breach_notify'] = 0;
|
232 |
self::$lss->options = $options;
|
233 |
|
|
|
|
|
|
|
|
|
234 |
self::$lss->delete_pw_force_change($this->user->ID);
|
235 |
|
236 |
try {
|
195 |
$actual = self::$lss->get_pw_force_change($this->user->ID);
|
196 |
$this->assertTrue($actual, 'get_pw_force_change() return value...');
|
197 |
|
198 |
+
self::$lss->delete_pw_force_change($this->user->ID);
|
199 |
+
|
200 |
+
$this->check_mail_file();
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* @depends test_wp_login__post_breach_threshold
|
205 |
+
*/
|
206 |
+
public function test_wp_login__post_breach_threshold_verified_ip() {
|
207 |
+
global $wpdb;
|
208 |
+
self::$mail_file_basename = __METHOD__;
|
209 |
+
|
210 |
+
$wpdb->query('SAVEPOINT pre_verified_ip');
|
211 |
+
|
212 |
+
$this->ip = '1.2.33.4';
|
213 |
+
$_SERVER['REMOTE_ADDR'] = $this->ip;
|
214 |
+
$this->network_ip = '1.2.33';
|
215 |
+
|
216 |
+
self::$lss->save_verified_ip($this->user->ID, $this->ip);
|
217 |
+
|
218 |
+
try {
|
219 |
+
// Do THE deed.
|
220 |
+
$actual = self::$lss->wp_login($this->user_name, $this->user);
|
221 |
+
} catch (Exception $e) {
|
222 |
+
$this->fail($e->getMessage());
|
223 |
+
}
|
224 |
+
$this->assertSame(13, $actual, 'Bad return value.');
|
225 |
+
|
226 |
+
$actual = self::$lss->get_pw_force_change($this->user->ID);
|
227 |
+
$this->assertFalse($actual, 'get_pw_force_change() return value...');
|
228 |
+
|
229 |
+
$wpdb->query('ROLLBACK TO pre_verified_ip');
|
230 |
+
|
231 |
$this->check_mail_file();
|
232 |
}
|
233 |
|
264 |
$options['login_fail_breach_notify'] = 0;
|
265 |
self::$lss->options = $options;
|
266 |
|
267 |
+
$this->ip = '1.2.38.4';
|
268 |
+
$_SERVER['REMOTE_ADDR'] = $this->ip;
|
269 |
+
$this->network_ip = '1.2.38';
|
270 |
+
|
271 |
self::$lss->delete_pw_force_change($this->user->ID);
|
272 |
|
273 |
try {
|
tests/TestCase.php
CHANGED
@@ -360,7 +360,7 @@ abstract class TestCase extends PHPUnit_Framework_TestCase {
|
|
360 |
$contents = 'To: ' . implode(', ', (array) $to) . "\n"
|
361 |
. "Subject: $subject\n\n$message";
|
362 |
|
363 |
-
return file_put_contents(self::$mail_file, $contents);
|
364 |
}
|
365 |
|
366 |
/**
|
360 |
$contents = 'To: ' . implode(', ', (array) $to) . "\n"
|
361 |
. "Subject: $subject\n\n$message";
|
362 |
|
363 |
+
return file_put_contents(self::$mail_file, $contents, FILE_APPEND);
|
364 |
}
|
365 |
|
366 |
/**
|
tests/VerifiedIpTest.php
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Test the verified IP saving and retrieving
|
5 |
+
*
|
6 |
+
* @package login-security-solution
|
7 |
+
* @author Daniel Convissor <danielc@analysisandsolutions.com>
|
8 |
+
* @copyright The Analysis and Solutions Company, 2012
|
9 |
+
* @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Get the class we will use for testing
|
14 |
+
*/
|
15 |
+
require_once dirname(__FILE__) . '/TestCase.php';
|
16 |
+
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Test the verified IP saving and retrieving
|
20 |
+
*
|
21 |
+
* @package login-security-solution
|
22 |
+
* @author Daniel Convissor <danielc@analysisandsolutions.com>
|
23 |
+
* @copyright The Analysis and Solutions Company, 2012
|
24 |
+
* @license http://www.gnu.org/licenses/gpl-2.0.html GPLv2
|
25 |
+
*/
|
26 |
+
class VerifiedIpTest extends TestCase {
|
27 |
+
protected static $ip_1 = '1.2.3.4';
|
28 |
+
|
29 |
+
|
30 |
+
public static function setUpBeforeClass() {
|
31 |
+
parent::$db_needed = true;
|
32 |
+
parent::set_up_before_class();
|
33 |
+
}
|
34 |
+
|
35 |
+
public function setUp() {
|
36 |
+
parent::setUp();
|
37 |
+
}
|
38 |
+
|
39 |
+
|
40 |
+
public function test_get_verified_ips__empty() {
|
41 |
+
global $wpdb;
|
42 |
+
|
43 |
+
$wpdb->query('SAVEPOINT empty');
|
44 |
+
|
45 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
46 |
+
$this->assertSame(array(), $actual);
|
47 |
+
}
|
48 |
+
|
49 |
+
public function test_save_verified_ip__non_array_edge_case() {
|
50 |
+
update_user_meta($this->user->ID, self::$lss->umk_verified_ips, 'foo');
|
51 |
+
|
52 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
53 |
+
$this->assertEquals(array('foo'), $actual);
|
54 |
+
|
55 |
+
delete_user_meta($this->user->ID, self::$lss->umk_verified_ips);
|
56 |
+
}
|
57 |
+
|
58 |
+
public function test_save_verified_ip__new() {
|
59 |
+
$actual = self::$lss->save_verified_ip($this->user->ID, self::$ip_1);
|
60 |
+
$this->assertTrue($actual);
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* @depends test_save_verified_ip__new
|
65 |
+
*/
|
66 |
+
public function test_save_verified_ip__exists() {
|
67 |
+
$actual = self::$lss->save_verified_ip($this->user->ID, self::$ip_1);
|
68 |
+
$this->assertSame(1, $actual);
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* @depends test_save_verified_ip__exists
|
73 |
+
*/
|
74 |
+
public function test_get_verified_ips__one() {
|
75 |
+
global $wpdb;
|
76 |
+
|
77 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
78 |
+
$this->assertEquals(array(self::$ip_1), $actual);
|
79 |
+
|
80 |
+
$wpdb->query('ROLLBACK TO empty');
|
81 |
+
wp_cache_reset();
|
82 |
+
|
83 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
84 |
+
$this->assertSame(array(), $actual);
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* @depends test_get_verified_ips__one
|
89 |
+
*/
|
90 |
+
public function test_save_verified_ip__overflow() {
|
91 |
+
global $wpdb;
|
92 |
+
|
93 |
+
self::$lss->save_verified_ip($this->user->ID, 'a');
|
94 |
+
self::$lss->save_verified_ip($this->user->ID, 'b');
|
95 |
+
self::$lss->save_verified_ip($this->user->ID, 'c');
|
96 |
+
self::$lss->save_verified_ip($this->user->ID, 'd');
|
97 |
+
self::$lss->save_verified_ip($this->user->ID, 'e');
|
98 |
+
self::$lss->save_verified_ip($this->user->ID, 'f');
|
99 |
+
self::$lss->save_verified_ip($this->user->ID, 'g');
|
100 |
+
self::$lss->save_verified_ip($this->user->ID, 'h');
|
101 |
+
self::$lss->save_verified_ip($this->user->ID, 'i');
|
102 |
+
self::$lss->save_verified_ip($this->user->ID, 'j');
|
103 |
+
self::$lss->save_verified_ip($this->user->ID, 'k');
|
104 |
+
|
105 |
+
$expected = range('b', 'k');
|
106 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
107 |
+
$this->assertEquals($expected, $actual);
|
108 |
+
|
109 |
+
$wpdb->query('ROLLBACK TO empty');
|
110 |
+
wp_cache_reset();
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* @depends test_save_verified_ip__overflow
|
115 |
+
*/
|
116 |
+
public function test_password_reset__normal() {
|
117 |
+
global $wpdb;
|
118 |
+
|
119 |
+
$ip = '3.4.5.6';
|
120 |
+
$_SERVER['REMOTE_ADDR'] = $ip;
|
121 |
+
|
122 |
+
$actual = self::$lss->password_reset($this->user, 'some 1 Needs!');
|
123 |
+
$this->assertNull($actual, 'password_reset() should return null.');
|
124 |
+
|
125 |
+
// Check the outcome.
|
126 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
127 |
+
$this->assertSame(array($ip), $actual, 'Expected IP missing.');
|
128 |
+
|
129 |
+
$wpdb->query('ROLLBACK TO empty');
|
130 |
+
wp_cache_reset();
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* @depends test_password_reset__normal
|
135 |
+
*/
|
136 |
+
public function test_profile_update__normal() {
|
137 |
+
global $current_user;
|
138 |
+
|
139 |
+
$ip = '4.5.6.7';
|
140 |
+
$_SERVER['REMOTE_ADDR'] = $ip;
|
141 |
+
// So user id = current user id in our profile update errors method.
|
142 |
+
$current_user = $this->user;
|
143 |
+
|
144 |
+
$errors = new WP_Error;
|
145 |
+
$actual = self::$lss->user_profile_update_errors($errors, 1, $this->user);
|
146 |
+
$this->assertTrue($actual, 'Bad return value.');
|
147 |
+
|
148 |
+
// Check the outcome.
|
149 |
+
$actual = self::$lss->get_verified_ips($this->user->ID);
|
150 |
+
$this->assertSame(array($ip), $actual, 'Expected IP missing.');
|
151 |
+
}
|
152 |
+
}
|
tests/expected/LoginFailTest--test_wp_login__post_breach_threshold_verified_ip
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
To: %a
|
2 |
+
Subject: POTENTIAL INTRUSION AT %a
|
3 |
+
|
4 |
+
Your website, %s, may have been broken in to.
|
5 |
+
|
6 |
+
Someone just logged in using the following components. Prior to that, some combination of those components were a part of 4 failed attempts to log in during the past 60 minutes:
|
7 |
+
|
8 |
+
Component Count Value from Current Attempt
|
9 |
+
------------ ----- --------------------------------
|
10 |
+
Network IP 0 1.2.33
|
11 |
+
Username 4 test
|
12 |
+
Password MD5 %d %s
|
13 |
+
|
14 |
+
The user's current IP address is one they have verified with your site in the past. Therefore, the user will NOT be required to confirm their identity via the password reset process. An email will be sent to them, just in case this actually was a breach.
|
15 |
+
To: %a
|
16 |
+
Subject: POTENTIAL INTRUSION AT %a
|
17 |
+
|
18 |
+
Someone just logged into your '%s' account at %s. Was it you that logged in? We are asking because the site is being attacked.
|
19 |
+
|
20 |
+
IF IT WAS NOT YOU, please do the following right away:
|
21 |
+
|
22 |
+
1) Log into %s and change your password.
|
23 |
+
|
24 |
+
2) Send an email to %s letting them know it was not you who logged in.
|