Two-Factor - Version 0.4.5

Version Description

Download this release

Release Info

Developer kasparsd
Plugin Icon 128x128 Two-Factor
Version 0.4.5
Comparing to
See all releases

Code changes from version 0.7.0 to 0.4.5

LICENSE.md DELETED
@@ -1,280 +0,0 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 2, June 1991
3
-
4
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
- Everyone is permitted to copy and distribute verbatim copies
7
- of this license document, but changing it is not allowed.
8
-
9
- Preamble
10
-
11
- The licenses for most software are designed to take away your
12
- freedom to share and change it. By contrast, the GNU General Public
13
- License is intended to guarantee your freedom to share and change free
14
- software--to make sure the software is free for all its users. This
15
- General Public License applies to most of the Free Software
16
- Foundation's software and to any other program whose authors commit to
17
- using it. (Some other Free Software Foundation software is covered by
18
- the GNU Lesser General Public License instead.) You can apply it to
19
- your programs, too.
20
-
21
- When we speak of free software, we are referring to freedom, not
22
- price. Our General Public Licenses are designed to make sure that you
23
- have the freedom to distribute copies of free software (and charge for
24
- this service if you wish), that you receive source code or can get it
25
- if you want it, that you can change the software or use pieces of it
26
- in new free programs; and that you know you can do these things.
27
-
28
- To protect your rights, we need to make restrictions that forbid
29
- anyone to deny you these rights or to ask you to surrender the rights.
30
- These restrictions translate to certain responsibilities for you if you
31
- distribute copies of the software, or if you modify it.
32
-
33
- For example, if you distribute copies of such a program, whether
34
- gratis or for a fee, you must give the recipients all the rights that
35
- you have. You must make sure that they, too, receive or can get the
36
- source code. And you must show them these terms so they know their
37
- rights.
38
-
39
- We protect your rights with two steps: (1) copyright the software, and
40
- (2) offer you this license which gives you legal permission to copy,
41
- distribute and/or modify the software.
42
-
43
- Also, for each author's protection and ours, we want to make certain
44
- that everyone understands that there is no warranty for this free
45
- software. If the software is modified by someone else and passed on, we
46
- want its recipients to know that what they have is not the original, so
47
- that any problems introduced by others will not reflect on the original
48
- authors' reputations.
49
-
50
- Finally, any free program is threatened constantly by software
51
- patents. We wish to avoid the danger that redistributors of a free
52
- program will individually obtain patent licenses, in effect making the
53
- program proprietary. To prevent this, we have made it clear that any
54
- patent must be licensed for everyone's free use or not licensed at all.
55
-
56
- The precise terms and conditions for copying, distribution and
57
- modification follow.
58
-
59
- GNU GENERAL PUBLIC LICENSE
60
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
-
62
- 0. This License applies to any program or other work which contains
63
- a notice placed by the copyright holder saying it may be distributed
64
- under the terms of this General Public License. The "Program", below,
65
- refers to any such program or work, and a "work based on the Program"
66
- means either the Program or any derivative work under copyright law:
67
- that is to say, a work containing the Program or a portion of it,
68
- either verbatim or with modifications and/or translated into another
69
- language. (Hereinafter, translation is included without limitation in
70
- the term "modification".) Each licensee is addressed as "you".
71
-
72
- Activities other than copying, distribution and modification are not
73
- covered by this License; they are outside its scope. The act of
74
- running the Program is not restricted, and the output from the Program
75
- is covered only if its contents constitute a work based on the
76
- Program (independent of having been made by running the Program).
77
- Whether that is true depends on what the Program does.
78
-
79
- 1. You may copy and distribute verbatim copies of the Program's
80
- source code as you receive it, in any medium, provided that you
81
- conspicuously and appropriately publish on each copy an appropriate
82
- copyright notice and disclaimer of warranty; keep intact all the
83
- notices that refer to this License and to the absence of any warranty;
84
- and give any other recipients of the Program a copy of this License
85
- along with the Program.
86
-
87
- You may charge a fee for the physical act of transferring a copy, and
88
- you may at your option offer warranty protection in exchange for a fee.
89
-
90
- 2. You may modify your copy or copies of the Program or any portion
91
- of it, thus forming a work based on the Program, and copy and
92
- distribute such modifications or work under the terms of Section 1
93
- above, provided that you also meet all of these conditions:
94
-
95
- a) You must cause the modified files to carry prominent notices
96
- stating that you changed the files and the date of any change.
97
-
98
- b) You must cause any work that you distribute or publish, that in
99
- whole or in part contains or is derived from the Program or any
100
- part thereof, to be licensed as a whole at no charge to all third
101
- parties under the terms of this License.
102
-
103
- c) If the modified program normally reads commands interactively
104
- when run, you must cause it, when started running for such
105
- interactive use in the most ordinary way, to print or display an
106
- announcement including an appropriate copyright notice and a
107
- notice that there is no warranty (or else, saying that you provide
108
- a warranty) and that users may redistribute the program under
109
- these conditions, and telling the user how to view a copy of this
110
- License. (Exception: if the Program itself is interactive but
111
- does not normally print such an announcement, your work based on
112
- the Program is not required to print an announcement.)
113
-
114
- These requirements apply to the modified work as a whole. If
115
- identifiable sections of that work are not derived from the Program,
116
- and can be reasonably considered independent and separate works in
117
- themselves, then this License, and its terms, do not apply to those
118
- sections when you distribute them as separate works. But when you
119
- distribute the same sections as part of a whole which is a work based
120
- on the Program, the distribution of the whole must be on the terms of
121
- this License, whose permissions for other licensees extend to the
122
- entire whole, and thus to each and every part regardless of who wrote it.
123
-
124
- Thus, it is not the intent of this section to claim rights or contest
125
- your rights to work written entirely by you; rather, the intent is to
126
- exercise the right to control the distribution of derivative or
127
- collective works based on the Program.
128
-
129
- In addition, mere aggregation of another work not based on the Program
130
- with the Program (or with a work based on the Program) on a volume of
131
- a storage or distribution medium does not bring the other work under
132
- the scope of this License.
133
-
134
- 3. You may copy and distribute the Program (or a work based on it,
135
- under Section 2) in object code or executable form under the terms of
136
- Sections 1 and 2 above provided that you also do one of the following:
137
-
138
- a) Accompany it with the complete corresponding machine-readable
139
- source code, which must be distributed under the terms of Sections
140
- 1 and 2 above on a medium customarily used for software interchange; or,
141
-
142
- b) Accompany it with a written offer, valid for at least three
143
- years, to give any third party, for a charge no more than your
144
- cost of physically performing source distribution, a complete
145
- machine-readable copy of the corresponding source code, to be
146
- distributed under the terms of Sections 1 and 2 above on a medium
147
- customarily used for software interchange; or,
148
-
149
- c) Accompany it with the information you received as to the offer
150
- to distribute corresponding source code. (This alternative is
151
- allowed only for noncommercial distribution and only if you
152
- received the program in object code or executable form with such
153
- an offer, in accord with Subsection b above.)
154
-
155
- The source code for a work means the preferred form of the work for
156
- making modifications to it. For an executable work, complete source
157
- code means all the source code for all modules it contains, plus any
158
- associated interface definition files, plus the scripts used to
159
- control compilation and installation of the executable. However, as a
160
- special exception, the source code distributed need not include
161
- anything that is normally distributed (in either source or binary
162
- form) with the major components (compiler, kernel, and so on) of the
163
- operating system on which the executable runs, unless that component
164
- itself accompanies the executable.
165
-
166
- If distribution of executable or object code is made by offering
167
- access to copy from a designated place, then offering equivalent
168
- access to copy the source code from the same place counts as
169
- distribution of the source code, even though third parties are not
170
- compelled to copy the source along with the object code.
171
-
172
- 4. You may not copy, modify, sublicense, or distribute the Program
173
- except as expressly provided under this License. Any attempt
174
- otherwise to copy, modify, sublicense or distribute the Program is
175
- void, and will automatically terminate your rights under this License.
176
- However, parties who have received copies, or rights, from you under
177
- this License will not have their licenses terminated so long as such
178
- parties remain in full compliance.
179
-
180
- 5. You are not required to accept this License, since you have not
181
- signed it. However, nothing else grants you permission to modify or
182
- distribute the Program or its derivative works. These actions are
183
- prohibited by law if you do not accept this License. Therefore, by
184
- modifying or distributing the Program (or any work based on the
185
- Program), you indicate your acceptance of this License to do so, and
186
- all its terms and conditions for copying, distributing or modifying
187
- the Program or works based on it.
188
-
189
- 6. Each time you redistribute the Program (or any work based on the
190
- Program), the recipient automatically receives a license from the
191
- original licensor to copy, distribute or modify the Program subject to
192
- these terms and conditions. You may not impose any further
193
- restrictions on the recipients' exercise of the rights granted herein.
194
- You are not responsible for enforcing compliance by third parties to
195
- this License.
196
-
197
- 7. If, as a consequence of a court judgment or allegation of patent
198
- infringement or for any other reason (not limited to patent issues),
199
- conditions are imposed on you (whether by court order, agreement or
200
- otherwise) that contradict the conditions of this License, they do not
201
- excuse you from the conditions of this License. If you cannot
202
- distribute so as to satisfy simultaneously your obligations under this
203
- License and any other pertinent obligations, then as a consequence you
204
- may not distribute the Program at all. For example, if a patent
205
- license would not permit royalty-free redistribution of the Program by
206
- all those who receive copies directly or indirectly through you, then
207
- the only way you could satisfy both it and this License would be to
208
- refrain entirely from distribution of the Program.
209
-
210
- If any portion of this section is held invalid or unenforceable under
211
- any particular circumstance, the balance of the section is intended to
212
- apply and the section as a whole is intended to apply in other
213
- circumstances.
214
-
215
- It is not the purpose of this section to induce you to infringe any
216
- patents or other property right claims or to contest validity of any
217
- such claims; this section has the sole purpose of protecting the
218
- integrity of the free software distribution system, which is
219
- implemented by public license practices. Many people have made
220
- generous contributions to the wide range of software distributed
221
- through that system in reliance on consistent application of that
222
- system; it is up to the author/donor to decide if he or she is willing
223
- to distribute software through any other system and a licensee cannot
224
- impose that choice.
225
-
226
- This section is intended to make thoroughly clear what is believed to
227
- be a consequence of the rest of this License.
228
-
229
- 8. If the distribution and/or use of the Program is restricted in
230
- certain countries either by patents or by copyrighted interfaces, the
231
- original copyright holder who places the Program under this License
232
- may add an explicit geographical distribution limitation excluding
233
- those countries, so that distribution is permitted only in or among
234
- countries not thus excluded. In such case, this License incorporates
235
- the limitation as if written in the body of this License.
236
-
237
- 9. The Free Software Foundation may publish revised and/or new versions
238
- of the General Public License from time to time. Such new versions will
239
- be similar in spirit to the present version, but may differ in detail to
240
- address new problems or concerns.
241
-
242
- Each version is given a distinguishing version number. If the Program
243
- specifies a version number of this License which applies to it and "any
244
- later version", you have the option of following the terms and conditions
245
- either of that version or of any later version published by the Free
246
- Software Foundation. If the Program does not specify a version number of
247
- this License, you may choose any version ever published by the Free Software
248
- Foundation.
249
-
250
- 10. If you wish to incorporate parts of the Program into other free
251
- programs whose distribution conditions are different, write to the author
252
- to ask for permission. For software which is copyrighted by the Free
253
- Software Foundation, write to the Free Software Foundation; we sometimes
254
- make exceptions for this. Our decision will be guided by the two goals
255
- of preserving the free status of all derivatives of our free software and
256
- of promoting the sharing and reuse of software generally.
257
-
258
- NO WARRANTY
259
-
260
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
- FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
- OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
- PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
- OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
- TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
- PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
- REPAIR OR CORRECTION.
269
-
270
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
- WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
- REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
- INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
- OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
- TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
- YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
- PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
- POSSIBILITY OF SUCH DAMAGES.
279
-
280
- END OF TERMS AND CONDITIONS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
class-two-factor-compat.php DELETED
@@ -1,55 +0,0 @@
1
- <?php
2
- /**
3
- * A compatibility layer for some of the most popular plugins.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
- /**
9
- * A compatibility layer for some of the most popular plugins.
10
- *
11
- * Should be used with care because ideally we wouldn't need
12
- * any integration specific code for this plugin. Everything should
13
- * be handled through clever use of hooks and best practices.
14
- */
15
- class Two_Factor_Compat {
16
- /**
17
- * Initialize all the custom hooks as necessary.
18
- *
19
- * @return void
20
- */
21
- public function init() {
22
- /**
23
- * Jetpack
24
- *
25
- * @see https://wordpress.org/plugins/jetpack/
26
- */
27
- add_filter( 'two_factor_rememberme', array( $this, 'jetpack_rememberme' ) );
28
- }
29
-
30
- /**
31
- * Jetpack single sign-on wants long-lived sessions for users.
32
- *
33
- * @param boolean $rememberme Current state of the "remember me" toggle.
34
- *
35
- * @return boolean
36
- */
37
- public function jetpack_rememberme( $rememberme ) {
38
- $action = filter_input( INPUT_GET, 'action', FILTER_SANITIZE_STRING );
39
-
40
- if ( 'jetpack-sso' === $action && $this->jetpack_is_sso_active() ) {
41
- return true;
42
- }
43
-
44
- return $rememberme;
45
- }
46
-
47
- /**
48
- * Helper to detect the presence of the active SSO module.
49
- *
50
- * @return boolean
51
- */
52
- public function jetpack_is_sso_active() {
53
- return ( method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'sso' ) );
54
- }
55
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
class-two-factor-core.php → class.two-factor-core.php RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Two Factore Core Class.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Class for creating two factor authorization.
10
  *
@@ -33,38 +27,14 @@ class Two_Factor_Core {
33
  *
34
  * @type string
35
  */
36
- const USER_META_NONCE_KEY = '_two_factor_nonce';
37
-
38
- /**
39
- * URL query paramater used for our custom actions.
40
- *
41
- * @var string
42
- */
43
- const USER_SETTINGS_ACTION_QUERY_VAR = 'two_factor_action';
44
-
45
- /**
46
- * Nonce key for user settings.
47
- *
48
- * @var string
49
- */
50
- const USER_SETTINGS_ACTION_NONCE_QUERY_ARG = '_two_factor_action_nonce';
51
-
52
- /**
53
- * Keep track of all the password-based authentication sessions that
54
- * need to invalidated before the second factor authentication.
55
- *
56
- * @var array
57
- */
58
- private static $password_auth_tokens = array();
59
 
60
  /**
61
  * Set up filters and actions.
62
  *
63
- * @param object $compat A compaitbility later for plugins.
64
- *
65
  * @since 0.1-dev
66
  */
67
- public static function add_hooks( $compat ) {
68
  add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
69
  add_action( 'init', array( __CLASS__, 'get_providers' ) );
70
  add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );
@@ -78,22 +48,8 @@ class Two_Factor_Core {
78
  add_filter( 'wpmu_users_columns', array( __CLASS__, 'filter_manage_users_columns' ) );
79
  add_filter( 'manage_users_custom_column', array( __CLASS__, 'manage_users_custom_column' ), 10, 3 );
80
 
81
- /**
82
- * Keep track of all the user sessions for which we need to invalidate the
83
- * authentication cookies set during the initial password check.
84
- *
85
- * Is there a better way of doing this?
86
- */
87
- add_action( 'set_auth_cookie', array( __CLASS__, 'collect_auth_cookie_tokens' ) );
88
- add_action( 'set_logged_in_cookie', array( __CLASS__, 'collect_auth_cookie_tokens' ) );
89
-
90
  // Run only after the core wp_authenticate_username_password() check.
91
  add_filter( 'authenticate', array( __CLASS__, 'filter_authenticate' ), 50 );
92
-
93
- add_action( 'admin_init', array( __CLASS__, 'trigger_user_settings_action' ) );
94
- add_filter( 'two_factor_providers', array( __CLASS__, 'enable_dummy_method_for_debug' ) );
95
-
96
- $compat->init();
97
  }
98
 
99
  /**
@@ -114,11 +70,11 @@ class Two_Factor_Core {
114
  */
115
  public static function get_providers() {
116
  $providers = array(
117
- 'Two_Factor_Email' => TWO_FACTOR_DIR . 'providers/class-two-factor-email.php',
118
- 'Two_Factor_Totp' => TWO_FACTOR_DIR . 'providers/class-two-factor-totp.php',
119
- 'Two_Factor_FIDO_U2F' => TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f.php',
120
- 'Two_Factor_Backup_Codes' => TWO_FACTOR_DIR . 'providers/class-two-factor-backup-codes.php',
121
- 'Two_Factor_Dummy' => TWO_FACTOR_DIR . 'providers/class-two-factor-dummy.php',
122
  );
123
 
124
  /**
@@ -135,20 +91,18 @@ class Two_Factor_Core {
135
  // FIDO U2F is PHP 5.3+ only.
136
  if ( isset( $providers['Two_Factor_FIDO_U2F'] ) && version_compare( PHP_VERSION, '5.3.0', '<' ) ) {
137
  unset( $providers['Two_Factor_FIDO_U2F'] );
138
- trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
139
- sprintf(
140
  /* translators: %s: version number */
141
- __( 'FIDO U2F is not available because you are using PHP %s. (Requires 5.3 or greater)', 'two-factor' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
142
- PHP_VERSION
143
- )
144
- );
145
  }
146
 
147
  /**
148
  * For each filtered provider,
149
  */
150
  foreach ( $providers as $class => $path ) {
151
- include_once $path;
152
 
153
  /**
154
  * Confirm that it's been successfully included before instantiating.
@@ -165,149 +119,6 @@ class Two_Factor_Core {
165
  return $providers;
166
  }
167
 
168
- /**
169
- * Enable the dummy method only during debugging.
170
- *
171
- * @param array $methods List of enabled methods.
172
- *
173
- * @return array
174
- */
175
- public static function enable_dummy_method_for_debug( $methods ) {
176
- if ( ! self::is_wp_debug() ) {
177
- unset( $methods['Two_Factor_Dummy'] );
178
- }
179
-
180
- return $methods;
181
- }
182
-
183
- /**
184
- * Check if the debug mode is enabled.
185
- *
186
- * @return boolean
187
- */
188
- protected static function is_wp_debug() {
189
- return ( defined( 'WP_DEBUG' ) && WP_DEBUG );
190
- }
191
-
192
- /**
193
- * Get the user settings page URL.
194
- *
195
- * Fetch this from the plugin core after we introduce proper dependency injection
196
- * and get away from the singletons at the provider level (should be handled by core).
197
- *
198
- * @param integer $user_id User ID.
199
- *
200
- * @return string
201
- */
202
- protected static function get_user_settings_page_url( $user_id ) {
203
- $page = 'user-edit.php';
204
-
205
- if ( defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) {
206
- $page = 'profile.php';
207
- }
208
-
209
- return add_query_arg(
210
- array(
211
- 'user_id' => intval( $user_id ),
212
- ),
213
- self_admin_url( $page )
214
- );
215
- }
216
-
217
- /**
218
- * Get the URL for resetting the secret token.
219
- *
220
- * @param integer $user_id User ID.
221
- * @param string $action Custom two factor action key.
222
- *
223
- * @return string
224
- */
225
- public static function get_user_update_action_url( $user_id, $action ) {
226
- return wp_nonce_url(
227
- add_query_arg(
228
- array(
229
- self::USER_SETTINGS_ACTION_QUERY_VAR => $action,
230
- ),
231
- self::get_user_settings_page_url( $user_id )
232
- ),
233
- sprintf( '%d-%s', $user_id, $action ),
234
- self::USER_SETTINGS_ACTION_NONCE_QUERY_ARG
235
- );
236
- }
237
-
238
- /**
239
- * Check if a user action is valid.
240
- *
241
- * @param integer $user_id User ID.
242
- * @param string $action User action ID.
243
- *
244
- * @return boolean
245
- */
246
- public static function is_valid_user_action( $user_id, $action ) {
247
- $request_nonce = filter_input( INPUT_GET, self::USER_SETTINGS_ACTION_NONCE_QUERY_ARG, FILTER_SANITIZE_STRING );
248
-
249
- return wp_verify_nonce(
250
- $request_nonce,
251
- sprintf( '%d-%s', $user_id, $action )
252
- );
253
- }
254
-
255
- /**
256
- * Get the ID of the user being edited.
257
- *
258
- * @return integer
259
- */
260
- public static function current_user_being_edited() {
261
- // Try to resolve the user ID from the request first.
262
- if ( ! empty( $_REQUEST['user_id'] ) ) {
263
- $user_id = intval( $_REQUEST['user_id'] );
264
-
265
- if ( current_user_can( 'edit_user', $user_id ) ) {
266
- return $user_id;
267
- }
268
- }
269
-
270
- return get_current_user_id();
271
- }
272
-
273
- /**
274
- * Trigger our custom update action if a valid
275
- * action request is detected and passes the nonce check.
276
- *
277
- * @return void
278
- */
279
- public static function trigger_user_settings_action() {
280
- $action = filter_input( INPUT_GET, self::USER_SETTINGS_ACTION_QUERY_VAR, FILTER_SANITIZE_STRING );
281
- $user_id = self::current_user_being_edited();
282
-
283
- if ( ! empty( $action ) && self::is_valid_user_action( $user_id, $action ) ) {
284
- /**
285
- * This action is triggered when a valid Two Factor settings
286
- * action is detected and it passes the nonce validation.
287
- *
288
- * @param integer $user_id User ID.
289
- * @param string $action Settings action.
290
- */
291
- do_action( 'two_factor_user_settings_action', $user_id, $action );
292
- }
293
- }
294
-
295
- /**
296
- * Keep track of all the authentication cookies that need to be
297
- * invalidated before the second factor authentication.
298
- *
299
- * @param string $cookie Cookie string.
300
- *
301
- * @return void
302
- */
303
- public static function collect_auth_cookie_tokens( $cookie ) {
304
- $parsed = wp_parse_auth_cookie( $cookie );
305
-
306
- if ( ! empty( $parsed['token'] ) ) {
307
- self::$password_auth_tokens[] = $parsed['token'];
308
- }
309
- }
310
-
311
  /**
312
  * Get all Two-Factor Auth providers that are enabled for the specified|current user.
313
  *
@@ -326,13 +137,7 @@ class Two_Factor_Core {
326
  }
327
  $enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
328
 
329
- /**
330
- * Filter the enabled two-factor authentication providers for this user.
331
- *
332
- * @param array $enabled_providers The enabled providers.
333
- * @param int $user_id The user ID.
334
- */
335
- return apply_filters( 'two_factor_enabled_providers_for_user', $enabled_providers, $user->ID );
336
  }
337
 
338
  /**
@@ -351,7 +156,7 @@ class Two_Factor_Core {
351
  $configured_providers = array();
352
 
353
  foreach ( $providers as $classname => $provider ) {
354
- if ( in_array( $classname, $enabled_providers, true ) && $provider->is_available_for_user( $user ) ) {
355
  $configured_providers[ $classname ] = $provider;
356
  }
357
  }
@@ -430,35 +235,12 @@ class Two_Factor_Core {
430
  return;
431
  }
432
 
433
- // Invalidate the current login session to prevent from being re-used.
434
- self::destroy_current_session_for_user( $user );
435
-
436
- // Also clear the cookies which are no longer valid.
437
  wp_clear_auth_cookie();
438
 
439
  self::show_two_factor_login( $user );
440
  exit;
441
  }
442
 
443
- /**
444
- * Destroy the known password-based authentication sessions for the current user.
445
- *
446
- * Is there a better way of finding the current session token without
447
- * having access to the authentication cookies which are just being set
448
- * on the first password-based authentication request.
449
- *
450
- * @param \WP_User $user User object.
451
- *
452
- * @return void
453
- */
454
- public static function destroy_current_session_for_user( $user ) {
455
- $session_manager = WP_Session_Tokens::get_instance( $user->ID );
456
-
457
- foreach ( self::$password_auth_tokens as $auth_token ) {
458
- $session_manager->destroy( $auth_token );
459
- }
460
- }
461
-
462
  /**
463
  * Prevent login through XML-RPC and REST API for users with at least one
464
  * two-factor method enabled.
@@ -536,33 +318,29 @@ class Two_Factor_Core {
536
  * @since 0.1-dev
537
  */
538
  public static function backup_2fa() {
539
- $wp_auth_id = filter_input( INPUT_GET, 'wp-auth-id', FILTER_SANITIZE_NUMBER_INT );
540
- $nonce = filter_input( INPUT_GET, 'wp-auth-nonce', FILTER_SANITIZE_STRING );
541
- $provider = filter_input( INPUT_GET, 'provider', FILTER_SANITIZE_STRING );
542
-
543
- if ( ! $wp_auth_id || ! $nonce || ! $provider ) {
544
  return;
545
  }
546
 
547
- $user = get_userdata( $wp_auth_id );
548
  if ( ! $user ) {
549
  return;
550
  }
551
 
 
552
  if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
553
  wp_safe_redirect( get_bloginfo( 'url' ) );
554
  exit;
555
  }
556
 
557
  $providers = self::get_available_providers_for_user( $user );
558
- if ( isset( $providers[ $provider ] ) ) {
559
- $provider = $providers[ $provider ];
560
  } else {
561
  wp_die( esc_html__( 'Cheatin&#8217; uh?', 'two-factor' ), 403 );
562
  }
563
 
564
- $redirect_to = filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_URL );
565
- self::login_html( $user, $nonce, $redirect_to, '', $provider );
566
 
567
  exit;
568
  }
@@ -588,14 +366,17 @@ class Two_Factor_Core {
588
  $provider_class = get_class( $provider );
589
 
590
  $available_providers = self::get_available_providers_for_user( $user );
591
- $backup_providers = array_diff_key( $available_providers, array( $provider_class => null ) );
592
- $interim_login = isset( $_REQUEST['interim-login'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
593
 
594
- $rememberme = intval( self::rememberme() );
 
 
 
595
 
596
  if ( ! function_exists( 'login_header' ) ) {
597
  // We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
598
- include_once TWO_FACTOR_DIR . 'includes/function.login-header.php';
599
  }
600
 
601
  login_header();
@@ -609,11 +390,11 @@ class Two_Factor_Core {
609
  <input type="hidden" name="provider" id="provider" value="<?php echo esc_attr( $provider_class ); ?>" />
610
  <input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo esc_attr( $user->ID ); ?>" />
611
  <input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo esc_attr( $login_nonce ); ?>" />
612
- <?php if ( $interim_login ) { ?>
613
  <input type="hidden" name="interim-login" value="1" />
614
- <?php } else { ?>
615
  <input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_to ); ?>" />
616
- <?php } ?>
617
  <input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>" />
618
 
619
  <?php $provider->authentication_page( $user ); ?>
@@ -622,8 +403,8 @@ class Two_Factor_Core {
622
  <?php
623
  if ( 1 === count( $backup_providers ) ) :
624
  $backup_classname = key( $backup_providers );
625
- $backup_provider = $backup_providers[ $backup_classname ];
626
- $login_url = self::login_url(
627
  array(
628
  'action' => 'backup_2fa',
629
  'provider' => $backup_classname,
@@ -717,8 +498,7 @@ class Two_Factor_Core {
717
 
718
  <?php
719
  /** This action is documented in wp-login.php */
720
- do_action( 'login_footer' );
721
- ?>
722
  <div class="clear"></div>
723
  </body>
724
  </html>
@@ -752,11 +532,11 @@ class Two_Factor_Core {
752
  * @return array
753
  */
754
  public static function create_login_nonce( $user_id ) {
755
- $login_nonce = array();
756
  try {
757
  $login_nonce['key'] = bin2hex( random_bytes( 32 ) );
758
- } catch ( Exception $ex ) {
759
- $login_nonce['key'] = wp_hash( $user_id . wp_rand() . microtime(), 'nonce' );
760
  }
761
  $login_nonce['expiration'] = time() + HOUR_IN_SECONDS;
762
 
@@ -808,28 +588,25 @@ class Two_Factor_Core {
808
  * @since 0.1-dev
809
  */
810
  public static function login_form_validate_2fa() {
811
- $wp_auth_id = filter_input( INPUT_POST, 'wp-auth-id', FILTER_SANITIZE_NUMBER_INT );
812
- $nonce = filter_input( INPUT_POST, 'wp-auth-nonce', FILTER_SANITIZE_STRING );
813
-
814
- if ( ! $wp_auth_id || ! $nonce ) {
815
  return;
816
  }
817
 
818
- $user = get_userdata( $wp_auth_id );
819
  if ( ! $user ) {
820
  return;
821
  }
822
 
 
823
  if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
824
  wp_safe_redirect( get_bloginfo( 'url' ) );
825
  exit;
826
  }
827
 
828
- $provider = filter_input( INPUT_POST, 'provider', FILTER_SANITIZE_STRING );
829
- if ( $provider ) {
830
  $providers = self::get_available_providers_for_user( $user );
831
- if ( isset( $providers[ $provider ] ) ) {
832
- $provider = $providers[ $provider ];
833
  } else {
834
  wp_die( esc_html__( 'Cheatin&#8217; uh?', 'two-factor' ), 403 );
835
  }
@@ -870,11 +647,9 @@ class Two_Factor_Core {
870
 
871
  wp_set_auth_cookie( $user->ID, $rememberme );
872
 
873
- do_action( 'two_factor_user_authenticated', $user );
874
-
875
  // Must be global because that's how login_header() uses it.
876
  global $interim_login;
877
- $interim_login = isset( $_REQUEST['interim-login'] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited,WordPress.Security.NonceVerification.Recommended
878
 
879
  if ( $interim_login ) {
880
  $customize_login = isset( $_REQUEST['customize-login'] );
@@ -882,16 +657,14 @@ class Two_Factor_Core {
882
  wp_enqueue_script( 'customize-base' );
883
  }
884
  $message = '<p class="message">' . __( 'You have logged in successfully.', 'two-factor' ) . '</p>';
885
- $interim_login = 'success'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
886
- login_header( '', $message );
887
- ?>
888
  </div>
889
  <?php
890
  /** This action is documented in wp-login.php */
891
- do_action( 'login_footer' );
892
- ?>
893
  <?php if ( $customize_login ) : ?>
894
- <script type="text/javascript">setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo esc_url( wp_customize_url() ); ?>', channel: 'login' }).send('login') }, 1000 );</script>
895
  <?php endif; ?>
896
  </body></html>
897
  <?php
@@ -947,10 +720,10 @@ class Two_Factor_Core {
947
  * @param WP_User $user WP_User object of the logged-in user.
948
  */
949
  public static function user_two_factor_options( $user ) {
950
- wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ), array(), TWO_FACTOR_VERSION );
951
 
952
  $enabled_providers = array_keys( self::get_available_providers_for_user( $user ) );
953
- $primary_provider = self::get_primary_provider_for_user( $user->ID );
954
 
955
  if ( ! empty( $primary_provider ) && is_object( $primary_provider ) ) {
956
  $primary_provider_key = get_class( $primary_provider );
@@ -962,7 +735,7 @@ class Two_Factor_Core {
962
 
963
  ?>
964
  <input type="hidden" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php /* Dummy input so $_POST value is passed when no providers are enabled. */ ?>" />
965
- <table class="form-table" id="two-factor-options">
966
  <tr>
967
  <th>
968
  <?php esc_html_e( 'Two-Factor Options', 'two-factor' ); ?>
@@ -979,24 +752,11 @@ class Two_Factor_Core {
979
  <tbody>
980
  <?php foreach ( self::get_providers() as $class => $object ) : ?>
981
  <tr>
982
- <th scope="row"><input type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $class ); ?>" <?php checked( in_array( $class, $enabled_providers, true ) ); ?> /></th>
983
  <th scope="row"><input type="radio" name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>" value="<?php echo esc_attr( $class ); ?>" <?php checked( $class, $primary_provider_key ); ?> /></th>
984
  <td>
985
- <?php
986
- $object->print_label();
987
-
988
- /**
989
- * Fires after user options are shown.
990
- *
991
- * Use the {@see 'two_factor_user_options_' . $class } hook instead.
992
- *
993
- * @deprecated 0.7.0
994
- *
995
- * @param WP_User $user The user.
996
- */
997
- do_action_deprecated( 'two-factor-user-options-' . $class, array( $user ), '0.7.0', 'two_factor_user_options_' . $class );
998
- do_action( 'two_factor_user_options_' . $class, $user );
999
- ?>
1000
  </td>
1001
  </tr>
1002
  <?php endforeach; ?>
@@ -1049,19 +809,4 @@ class Two_Factor_Core {
1049
  }
1050
  }
1051
  }
1052
-
1053
- /**
1054
- * Should the login session persist between sessions.
1055
- *
1056
- * @return boolean
1057
- */
1058
- public static function rememberme() {
1059
- $rememberme = false;
1060
-
1061
- if ( ! empty( $_REQUEST['rememberme'] ) ) {
1062
- $rememberme = true;
1063
- }
1064
-
1065
- return (bool) apply_filters( 'two_factor_rememberme', $rememberme );
1066
- }
1067
  }
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Class for creating two factor authorization.
4
  *
27
  *
28
  * @type string
29
  */
30
+ const USER_META_NONCE_KEY = '_two_factor_nonce';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  /**
33
  * Set up filters and actions.
34
  *
 
 
35
  * @since 0.1-dev
36
  */
37
+ public static function add_hooks() {
38
  add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
39
  add_action( 'init', array( __CLASS__, 'get_providers' ) );
40
  add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );
48
  add_filter( 'wpmu_users_columns', array( __CLASS__, 'filter_manage_users_columns' ) );
49
  add_filter( 'manage_users_custom_column', array( __CLASS__, 'manage_users_custom_column' ), 10, 3 );
50
 
 
 
 
 
 
 
 
 
 
51
  // Run only after the core wp_authenticate_username_password() check.
52
  add_filter( 'authenticate', array( __CLASS__, 'filter_authenticate' ), 50 );
 
 
 
 
 
53
  }
54
 
55
  /**
70
  */
71
  public static function get_providers() {
72
  $providers = array(
73
+ 'Two_Factor_Email' => TWO_FACTOR_DIR . 'providers/class.two-factor-email.php',
74
+ 'Two_Factor_Totp' => TWO_FACTOR_DIR . 'providers/class.two-factor-totp.php',
75
+ 'Two_Factor_FIDO_U2F' => TWO_FACTOR_DIR . 'providers/class.two-factor-fido-u2f.php',
76
+ 'Two_Factor_Backup_Codes' => TWO_FACTOR_DIR . 'providers/class.two-factor-backup-codes.php',
77
+ 'Two_Factor_Dummy' => TWO_FACTOR_DIR . 'providers/class.two-factor-dummy.php',
78
  );
79
 
80
  /**
91
  // FIDO U2F is PHP 5.3+ only.
92
  if ( isset( $providers['Two_Factor_FIDO_U2F'] ) && version_compare( PHP_VERSION, '5.3.0', '<' ) ) {
93
  unset( $providers['Two_Factor_FIDO_U2F'] );
94
+ trigger_error( sprintf( // WPCS: XSS OK.
 
95
  /* translators: %s: version number */
96
+ __( 'FIDO U2F is not available because you are using PHP %s. (Requires 5.3 or greater)', 'two-factor' ),
97
+ PHP_VERSION
98
+ ) );
 
99
  }
100
 
101
  /**
102
  * For each filtered provider,
103
  */
104
  foreach ( $providers as $class => $path ) {
105
+ include_once( $path );
106
 
107
  /**
108
  * Confirm that it's been successfully included before instantiating.
119
  return $providers;
120
  }
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  /**
123
  * Get all Two-Factor Auth providers that are enabled for the specified|current user.
124
  *
137
  }
138
  $enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
139
 
140
+ return $enabled_providers;
 
 
 
 
 
 
141
  }
142
 
143
  /**
156
  $configured_providers = array();
157
 
158
  foreach ( $providers as $classname => $provider ) {
159
+ if ( in_array( $classname, $enabled_providers ) && $provider->is_available_for_user( $user ) ) {
160
  $configured_providers[ $classname ] = $provider;
161
  }
162
  }
235
  return;
236
  }
237
 
 
 
 
 
238
  wp_clear_auth_cookie();
239
 
240
  self::show_two_factor_login( $user );
241
  exit;
242
  }
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  /**
245
  * Prevent login through XML-RPC and REST API for users with at least one
246
  * two-factor method enabled.
318
  * @since 0.1-dev
319
  */
320
  public static function backup_2fa() {
321
+ if ( ! isset( $_GET['wp-auth-id'], $_GET['wp-auth-nonce'], $_GET['provider'] ) ) {
 
 
 
 
322
  return;
323
  }
324
 
325
+ $user = get_userdata( $_GET['wp-auth-id'] );
326
  if ( ! $user ) {
327
  return;
328
  }
329
 
330
+ $nonce = $_GET['wp-auth-nonce'];
331
  if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
332
  wp_safe_redirect( get_bloginfo( 'url' ) );
333
  exit;
334
  }
335
 
336
  $providers = self::get_available_providers_for_user( $user );
337
+ if ( isset( $providers[ $_GET['provider'] ] ) ) {
338
+ $provider = $providers[ $_GET['provider'] ];
339
  } else {
340
  wp_die( esc_html__( 'Cheatin&#8217; uh?', 'two-factor' ), 403 );
341
  }
342
 
343
+ self::login_html( $user, $_GET['wp-auth-nonce'], $_GET['redirect_to'], '', $provider );
 
344
 
345
  exit;
346
  }
366
  $provider_class = get_class( $provider );
367
 
368
  $available_providers = self::get_available_providers_for_user( $user );
369
+ $backup_providers = array_diff_key( $available_providers, array( $provider_class => null ) );
370
+ $interim_login = isset( $_REQUEST['interim-login'] ); // WPCS: CSRF ok.
371
 
372
+ $rememberme = 0;
373
+ if ( isset( $_REQUEST['rememberme'] ) && $_REQUEST['rememberme'] ) {
374
+ $rememberme = 1;
375
+ }
376
 
377
  if ( ! function_exists( 'login_header' ) ) {
378
  // We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
379
+ include_once( TWO_FACTOR_DIR . 'includes/function.login-header.php' );
380
  }
381
 
382
  login_header();
390
  <input type="hidden" name="provider" id="provider" value="<?php echo esc_attr( $provider_class ); ?>" />
391
  <input type="hidden" name="wp-auth-id" id="wp-auth-id" value="<?php echo esc_attr( $user->ID ); ?>" />
392
  <input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="<?php echo esc_attr( $login_nonce ); ?>" />
393
+ <?php if ( $interim_login ) { ?>
394
  <input type="hidden" name="interim-login" value="1" />
395
+ <?php } else { ?>
396
  <input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_to ); ?>" />
397
+ <?php } ?>
398
  <input type="hidden" name="rememberme" id="rememberme" value="<?php echo esc_attr( $rememberme ); ?>" />
399
 
400
  <?php $provider->authentication_page( $user ); ?>
403
  <?php
404
  if ( 1 === count( $backup_providers ) ) :
405
  $backup_classname = key( $backup_providers );
406
+ $backup_provider = $backup_providers[ $backup_classname ];
407
+ $login_url = self::login_url(
408
  array(
409
  'action' => 'backup_2fa',
410
  'provider' => $backup_classname,
498
 
499
  <?php
500
  /** This action is documented in wp-login.php */
501
+ do_action( 'login_footer' ); ?>
 
502
  <div class="clear"></div>
503
  </body>
504
  </html>
532
  * @return array
533
  */
534
  public static function create_login_nonce( $user_id ) {
535
+ $login_nonce = array();
536
  try {
537
  $login_nonce['key'] = bin2hex( random_bytes( 32 ) );
538
+ } catch (Exception $ex) {
539
+ $login_nonce['key'] = wp_hash( $user_id . mt_rand() . microtime(), 'nonce' );
540
  }
541
  $login_nonce['expiration'] = time() + HOUR_IN_SECONDS;
542
 
588
  * @since 0.1-dev
589
  */
590
  public static function login_form_validate_2fa() {
591
+ if ( ! isset( $_POST['wp-auth-id'], $_POST['wp-auth-nonce'] ) ) {
 
 
 
592
  return;
593
  }
594
 
595
+ $user = get_userdata( $_POST['wp-auth-id'] );
596
  if ( ! $user ) {
597
  return;
598
  }
599
 
600
+ $nonce = $_POST['wp-auth-nonce'];
601
  if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
602
  wp_safe_redirect( get_bloginfo( 'url' ) );
603
  exit;
604
  }
605
 
606
+ if ( isset( $_POST['provider'] ) ) {
 
607
  $providers = self::get_available_providers_for_user( $user );
608
+ if ( isset( $providers[ $_POST['provider'] ] ) ) {
609
+ $provider = $providers[ $_POST['provider'] ];
610
  } else {
611
  wp_die( esc_html__( 'Cheatin&#8217; uh?', 'two-factor' ), 403 );
612
  }
647
 
648
  wp_set_auth_cookie( $user->ID, $rememberme );
649
 
 
 
650
  // Must be global because that's how login_header() uses it.
651
  global $interim_login;
652
+ $interim_login = isset( $_REQUEST['interim-login'] ); // WPCS: override ok.
653
 
654
  if ( $interim_login ) {
655
  $customize_login = isset( $_REQUEST['customize-login'] );
657
  wp_enqueue_script( 'customize-base' );
658
  }
659
  $message = '<p class="message">' . __( 'You have logged in successfully.', 'two-factor' ) . '</p>';
660
+ $interim_login = 'success'; // WPCS: override ok.
661
+ login_header( '', $message ); ?>
 
662
  </div>
663
  <?php
664
  /** This action is documented in wp-login.php */
665
+ do_action( 'login_footer' ); ?>
 
666
  <?php if ( $customize_login ) : ?>
667
+ <script type="text/javascript">setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo wp_customize_url(); /* WPCS: XSS OK. */ ?>', channel: 'login' }).send('login') }, 1000 );</script>
668
  <?php endif; ?>
669
  </body></html>
670
  <?php
720
  * @param WP_User $user WP_User object of the logged-in user.
721
  */
722
  public static function user_two_factor_options( $user ) {
723
+ wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ) );
724
 
725
  $enabled_providers = array_keys( self::get_available_providers_for_user( $user ) );
726
+ $primary_provider = self::get_primary_provider_for_user( $user->ID );
727
 
728
  if ( ! empty( $primary_provider ) && is_object( $primary_provider ) ) {
729
  $primary_provider_key = get_class( $primary_provider );
735
 
736
  ?>
737
  <input type="hidden" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php /* Dummy input so $_POST value is passed when no providers are enabled. */ ?>" />
738
+ <table class="form-table">
739
  <tr>
740
  <th>
741
  <?php esc_html_e( 'Two-Factor Options', 'two-factor' ); ?>
752
  <tbody>
753
  <?php foreach ( self::get_providers() as $class => $object ) : ?>
754
  <tr>
755
+ <th scope="row"><input type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $class ); ?>" <?php checked( in_array( $class, $enabled_providers ) ); ?> /></th>
756
  <th scope="row"><input type="radio" name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>" value="<?php echo esc_attr( $class ); ?>" <?php checked( $class, $primary_provider_key ); ?> /></th>
757
  <td>
758
+ <?php $object->print_label(); ?>
759
+ <?php do_action( 'two-factor-user-options-' . $class, $user ); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  </td>
761
  </tr>
762
  <?php endforeach; ?>
809
  }
810
  }
811
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
812
  }
docker-compose.yml DELETED
@@ -1,11 +0,0 @@
1
- version: '3.6'
2
-
3
- services:
4
-
5
- wpdevlib:
6
- build: ./tests/docker/wp-dev-lib
7
- working_dir: /var/www/html
8
- volumes:
9
- - .:/var/www/html
10
- environment:
11
- CHECK_SCOPE: all
 
 
 
 
 
 
 
 
 
 
 
includes/Google/u2f-api.js CHANGED
@@ -37,7 +37,7 @@ var js_api_version;
37
 
38
 
39
  /**
40
- * Message types for messages to/from the extension
41
  * @const
42
  * @enum {string}
43
  */
@@ -288,7 +288,7 @@ u2f.WrappedChromeRuntimePort_ = function(port) {
288
  u2f.formatSignRequest_ =
289
  function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
290
  if (js_api_version === undefined || js_api_version < 1.1) {
291
- // Adapt request to the 1.0 JS API.
292
  var signRequests = [];
293
  for (var i = 0; i < registeredKeys.length; i++) {
294
  signRequests[i] = {
@@ -305,7 +305,7 @@ u2f.formatSignRequest_ =
305
  requestId: reqId
306
  };
307
  }
308
- // JS 1.1 API.
309
  return {
310
  type: u2f.MessageTypes.U2F_SIGN_REQUEST,
311
  appId: appId,
@@ -327,7 +327,7 @@ u2f.formatSignRequest_ =
327
  u2f.formatRegisterRequest_ =
328
  function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
329
  if (js_api_version === undefined || js_api_version < 1.1) {
330
- // Adapt request to the 1.0 JS API.
331
  for (var i = 0; i < registerRequests.length; i++) {
332
  registerRequests[i].appId = appId;
333
  }
@@ -348,7 +348,7 @@ u2f.formatRegisterRequest_ =
348
  requestId: reqId
349
  };
350
  }
351
- // JS 1.1 API.
352
  return {
353
  type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
354
  appId: appId,
@@ -380,7 +380,7 @@ u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
380
  var name = eventName.toLowerCase();
381
  if (name == 'message' || name == 'onmessage') {
382
  this.port_.onMessage.addListener(function(message) {
383
- // Emulate a minimal MessageEvent object.
384
  handler({'data': message});
385
  });
386
  } else {
37
 
38
 
39
  /**
40
+ * Message types for messsages to/from the extension
41
  * @const
42
  * @enum {string}
43
  */
288
  u2f.formatSignRequest_ =
289
  function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
290
  if (js_api_version === undefined || js_api_version < 1.1) {
291
+ // Adapt request to the 1.0 JS API
292
  var signRequests = [];
293
  for (var i = 0; i < registeredKeys.length; i++) {
294
  signRequests[i] = {
305
  requestId: reqId
306
  };
307
  }
308
+ // JS 1.1 API
309
  return {
310
  type: u2f.MessageTypes.U2F_SIGN_REQUEST,
311
  appId: appId,
327
  u2f.formatRegisterRequest_ =
328
  function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
329
  if (js_api_version === undefined || js_api_version < 1.1) {
330
+ // Adapt request to the 1.0 JS API
331
  for (var i = 0; i < registerRequests.length; i++) {
332
  registerRequests[i].appId = appId;
333
  }
348
  requestId: reqId
349
  };
350
  }
351
+ // JS 1.1 API
352
  return {
353
  type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
354
  appId: appId,
380
  var name = eventName.toLowerCase();
381
  if (name == 'message' || name == 'onmessage') {
382
  this.port_.onMessage.addListener(function(message) {
383
+ // Emulate a minimal MessageEvent object
384
  handler({'data': message});
385
  });
386
  } else {
includes/Yubico/U2F.php CHANGED
@@ -164,7 +164,7 @@ class U2F
164
  $offs = 1;
165
  $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
166
  $offs += PUBKEY_LEN;
167
- // Decode the pubKey to make sure it's good.
168
  $tmpKey = $this->pubkey_to_pem($pubKey);
169
  if($tmpKey === null) {
170
  throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
@@ -175,7 +175,7 @@ class U2F
175
  $offs += $khLen;
176
  $registration->keyHandle = $this->base64u_encode($kh);
177
 
178
- // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes).
179
  $certLen = 4;
180
  $certLen += ($regData[$offs + 2] << 8);
181
  $certLen += $regData[$offs + 3];
164
  $offs = 1;
165
  $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
166
  $offs += PUBKEY_LEN;
167
+ // decode the pubKey to make sure it's good
168
  $tmpKey = $this->pubkey_to_pem($pubKey);
169
  if($tmpKey === null) {
170
  throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
175
  $offs += $khLen;
176
  $registration->keyHandle = $this->base64u_encode($kh);
177
 
178
+ // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
179
  $certLen = 4;
180
  $certLen += ($regData[$offs + 2] << 8);
181
  $certLen += $regData[$offs + 3];
includes/function.login-header.php CHANGED
@@ -14,7 +14,7 @@
14
  function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
15
  global $error, $interim_login, $action;
16
 
17
- // Don't index any of these forms.
18
  add_action( 'login_head', 'wp_no_robots' );
19
 
20
  add_action( 'login_head', 'wp_login_viewport_meta' );
@@ -179,7 +179,7 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
179
  if ( !empty( $message ) )
180
  echo $message . "\n";
181
 
182
- // In case a plugin uses $error rather than the $wp_errors object.
183
  if ( !empty( $error ) ) {
184
  $wp_error->add('error', $error);
185
  unset($error);
@@ -218,7 +218,7 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
218
  echo '<p class="message">' . apply_filters( 'login_messages', $messages ) . "</p>\n";
219
  }
220
  }
221
- } // End of login_header().
222
 
223
  function wp_login_viewport_meta() {
224
  ?>
14
  function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
15
  global $error, $interim_login, $action;
16
 
17
+ // Don't index any of these forms
18
  add_action( 'login_head', 'wp_no_robots' );
19
 
20
  add_action( 'login_head', 'wp_login_viewport_meta' );
179
  if ( !empty( $message ) )
180
  echo $message . "\n";
181
 
182
+ // In case a plugin uses $error rather than the $wp_errors object
183
  if ( !empty( $error ) ) {
184
  $wp_error->add('error', $error);
185
  unset($error);
218
  echo '<p class="message">' . apply_filters( 'login_messages', $messages ) . "</p>\n";
219
  }
220
  }
221
+ } // End of login_header()
222
 
223
  function wp_login_viewport_meta() {
224
  ?>
providers/{class-two-factor-backup-codes.php → class.two-factor-backup-codes.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Class for creating a backup codes provider.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Class for creating a backup codes provider.
10
  *
@@ -33,11 +27,11 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
33
  *
34
  * @since 0.1-dev
35
  */
36
- public static function get_instance() {
37
  static $instance;
38
  $class = __CLASS__;
39
  if ( ! is_a( $instance, $class ) ) {
40
- $instance = new $class();
41
  }
42
  return $instance;
43
  }
@@ -48,7 +42,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
48
  * @since 0.1-dev
49
  */
50
  protected function __construct() {
51
- add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
52
  add_action( 'admin_notices', array( $this, 'admin_notices' ) );
53
  add_action( 'wp_ajax_two_factor_backup_codes_generate', array( $this, 'ajax_generate_json' ) );
54
 
@@ -64,7 +58,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
64
  $user = wp_get_current_user();
65
 
66
  // Return if the provider is not enabled.
67
- if ( ! in_array( __CLASS__, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ), true ) ) {
68
  return;
69
  }
70
 
@@ -77,13 +71,9 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
77
  <p>
78
  <span>
79
  <?php
80
- wp_kses(
81
- sprintf(
82
- /* translators: %s: URL for code regeneration */
83
- __( 'Two-Factor: You are out of backup codes and need to <a href="%s">regenerate!</a>', 'two-factor' ),
84
- esc_url( get_edit_user_link( $user->ID ) . '#two-factor-backup-codes' )
85
- ),
86
- array( 'a' => array( 'href' => true ) )
87
  );
88
  ?>
89
  <span>
@@ -126,23 +116,19 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
126
  */
127
  public function user_options( $user ) {
128
  $ajax_nonce = wp_create_nonce( 'two-factor-backup-codes-generate-json-' . $user->ID );
129
- $count = self::codes_remaining_for_user( $user );
130
  ?>
131
  <p id="two-factor-backup-codes">
132
  <button type="button" class="button button-two-factor-backup-codes-generate button-secondary hide-if-no-js">
133
  <?php esc_html_e( 'Generate Verification Codes', 'two-factor' ); ?>
134
  </button>
135
- <span class="two-factor-backup-codes-count">
136
- <?php
137
- echo esc_html(
138
- sprintf(
139
  /* translators: %s: count */
140
- _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'two-factor' ),
141
- $count
142
- )
143
- );
144
- ?>
145
- </span>
146
  </p>
147
  <div class="two-factor-backup-codes-wrapper" style="display:none;">
148
  <ol class="two-factor-backup-codes-unused-codes"></ol>
@@ -177,7 +163,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
177
  // Update counter.
178
  $( '.two-factor-backup-codes-count' ).html( response.data.i18n.count );
179
 
180
- // Build the download link.
181
  var txt_data = 'data:application/text;charset=utf-8,' + '\n';
182
  txt_data += response.data.i18n.title.replace( /%s/g, document.domain ) + '\n\n';
183
 
@@ -200,11 +186,11 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
200
  * @since 0.1-dev
201
  *
202
  * @param WP_User $user WP_User object of the logged-in user.
203
- * @param array $args Optional arguments for assigning new codes.
204
  * @return array
205
  */
206
  public function generate_codes( $user, $args = '' ) {
207
- $codes = array();
208
  $codes_hashed = array();
209
 
210
  // Check for arguments.
@@ -220,9 +206,9 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
220
  }
221
 
222
  for ( $i = 0; $i < $num_codes; $i++ ) {
223
- $code = $this->get_code();
224
  $codes_hashed[] = wp_hash_password( $code );
225
- $codes[] = $code;
226
  unset( $code );
227
  }
228
 
@@ -238,13 +224,13 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
238
  * @since 0.1-dev
239
  */
240
  public function ajax_generate_json() {
241
- $user = get_user_by( 'id', filter_input( INPUT_POST, 'user_id', FILTER_SANITIZE_NUMBER_INT ) );
242
  check_ajax_referer( 'two-factor-backup-codes-generate-json-' . $user->ID, 'nonce' );
243
 
244
  // Setup the return data.
245
  $codes = $this->generate_codes( $user );
246
  $count = self::codes_remaining_for_user( $user );
247
- $i18n = array(
248
  /* translators: %s: count */
249
  'count' => esc_html( sprintf( _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'two-factor' ), $count ) ),
250
  /* translators: %s: the site's domain */
@@ -252,12 +238,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
252
  );
253
 
254
  // Send the response.
255
- wp_send_json_success(
256
- array(
257
- 'codes' => $codes,
258
- 'i18n' => $i18n,
259
- )
260
- );
261
  }
262
 
263
  /**
@@ -282,7 +263,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
282
  * @param WP_User $user WP_User object of the logged-in user.
283
  */
284
  public function authentication_page( $user ) {
285
- require_once ABSPATH . '/wp-admin/includes/template.php';
286
  ?>
287
  <p><?php esc_html_e( 'Enter a backup verification code.', 'two-factor' ); ?></p><br/>
288
  <p>
@@ -304,8 +285,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
304
  * @return boolean
305
  */
306
  public function validate_authentication( $user ) {
307
- $backup_code = isset( $_POST['two-factor-backup-code'] ) ? sanitize_text_field( wp_unslash( $_POST['two-factor-backup-code'] ) ) : false;
308
- return $this->validate_code( $user, filter_var( $backup_code, FILTER_SANITIZE_STRING ) );
309
  }
310
 
311
  /**
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Class for creating a backup codes provider.
4
  *
27
  *
28
  * @since 0.1-dev
29
  */
30
+ static function get_instance() {
31
  static $instance;
32
  $class = __CLASS__;
33
  if ( ! is_a( $instance, $class ) ) {
34
+ $instance = new $class;
35
  }
36
  return $instance;
37
  }
42
  * @since 0.1-dev
43
  */
44
  protected function __construct() {
45
+ add_action( 'two-factor-user-options-' . __CLASS__, array( $this, 'user_options' ) );
46
  add_action( 'admin_notices', array( $this, 'admin_notices' ) );
47
  add_action( 'wp_ajax_two_factor_backup_codes_generate', array( $this, 'ajax_generate_json' ) );
48
 
58
  $user = wp_get_current_user();
59
 
60
  // Return if the provider is not enabled.
61
+ if ( ! in_array( __CLASS__, Two_Factor_Core::get_enabled_providers_for_user( $user->ID ) ) ) {
62
  return;
63
  }
64
 
71
  <p>
72
  <span>
73
  <?php
74
+ printf( // WPCS: XSS OK.
75
+ __( 'Two-Factor: You are out of backup codes and need to <a href="%s">regenerate!</a>', 'two-factor' ),
76
+ esc_url( get_edit_user_link( $user->ID ) . '#two-factor-backup-codes' )
 
 
 
 
77
  );
78
  ?>
79
  <span>
116
  */
117
  public function user_options( $user ) {
118
  $ajax_nonce = wp_create_nonce( 'two-factor-backup-codes-generate-json-' . $user->ID );
119
+ $count = self::codes_remaining_for_user( $user );
120
  ?>
121
  <p id="two-factor-backup-codes">
122
  <button type="button" class="button button-two-factor-backup-codes-generate button-secondary hide-if-no-js">
123
  <?php esc_html_e( 'Generate Verification Codes', 'two-factor' ); ?>
124
  </button>
125
+ <span class="two-factor-backup-codes-count"><?php
126
+ echo esc_html( sprintf(
 
 
127
  /* translators: %s: count */
128
+ _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'two-factor' ),
129
+ $count
130
+ ) );
131
+ ?></span>
 
 
132
  </p>
133
  <div class="two-factor-backup-codes-wrapper" style="display:none;">
134
  <ol class="two-factor-backup-codes-unused-codes"></ol>
163
  // Update counter.
164
  $( '.two-factor-backup-codes-count' ).html( response.data.i18n.count );
165
 
166
+ // Build the download link
167
  var txt_data = 'data:application/text;charset=utf-8,' + '\n';
168
  txt_data += response.data.i18n.title.replace( /%s/g, document.domain ) + '\n\n';
169
 
186
  * @since 0.1-dev
187
  *
188
  * @param WP_User $user WP_User object of the logged-in user.
189
+ * @param array $args Optional arguments for assinging new codes.
190
  * @return array
191
  */
192
  public function generate_codes( $user, $args = '' ) {
193
+ $codes = array();
194
  $codes_hashed = array();
195
 
196
  // Check for arguments.
206
  }
207
 
208
  for ( $i = 0; $i < $num_codes; $i++ ) {
209
+ $code = $this->get_code();
210
  $codes_hashed[] = wp_hash_password( $code );
211
+ $codes[] = $code;
212
  unset( $code );
213
  }
214
 
224
  * @since 0.1-dev
225
  */
226
  public function ajax_generate_json() {
227
+ $user = get_user_by( 'id', sanitize_text_field( $_POST['user_id'] ) );
228
  check_ajax_referer( 'two-factor-backup-codes-generate-json-' . $user->ID, 'nonce' );
229
 
230
  // Setup the return data.
231
  $codes = $this->generate_codes( $user );
232
  $count = self::codes_remaining_for_user( $user );
233
+ $i18n = array(
234
  /* translators: %s: count */
235
  'count' => esc_html( sprintf( _n( '%s unused code remaining.', '%s unused codes remaining.', $count, 'two-factor' ), $count ) ),
236
  /* translators: %s: the site's domain */
238
  );
239
 
240
  // Send the response.
241
+ wp_send_json_success( array( 'codes' => $codes, 'i18n' => $i18n ) );
 
 
 
 
 
242
  }
243
 
244
  /**
263
  * @param WP_User $user WP_User object of the logged-in user.
264
  */
265
  public function authentication_page( $user ) {
266
+ require_once( ABSPATH . '/wp-admin/includes/template.php' );
267
  ?>
268
  <p><?php esc_html_e( 'Enter a backup verification code.', 'two-factor' ); ?></p><br/>
269
  <p>
285
  * @return boolean
286
  */
287
  public function validate_authentication( $user ) {
288
+ return $this->validate_code( $user, $_POST['two-factor-backup-code'] );
 
289
  }
290
 
291
  /**
providers/{class-two-factor-dummy.php → class.two-factor-dummy.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Class for creating a dummy provider.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Class for creating a dummy provider.
10
  *
@@ -19,11 +13,11 @@ class Two_Factor_Dummy extends Two_Factor_Provider {
19
  *
20
  * @since 0.1-dev
21
  */
22
- public static function get_instance() {
23
  static $instance;
24
  $class = __CLASS__;
25
  if ( ! is_a( $instance, $class ) ) {
26
- $instance = new $class();
27
  }
28
  return $instance;
29
  }
@@ -34,7 +28,7 @@ class Two_Factor_Dummy extends Two_Factor_Provider {
34
  * @since 0.1-dev
35
  */
36
  protected function __construct() {
37
- add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
38
  return parent::__construct();
39
  }
40
 
@@ -55,7 +49,7 @@ class Two_Factor_Dummy extends Two_Factor_Provider {
55
  * @param WP_User $user WP_User object of the logged-in user.
56
  */
57
  public function authentication_page( $user ) {
58
- require_once ABSPATH . '/wp-admin/includes/template.php';
59
  ?>
60
  <p><?php esc_html_e( 'Are you really you?', 'two-factor' ); ?></p>
61
  <?php
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Class for creating a dummy provider.
4
  *
13
  *
14
  * @since 0.1-dev
15
  */
16
+ static function get_instance() {
17
  static $instance;
18
  $class = __CLASS__;
19
  if ( ! is_a( $instance, $class ) ) {
20
+ $instance = new $class;
21
  }
22
  return $instance;
23
  }
28
  * @since 0.1-dev
29
  */
30
  protected function __construct() {
31
+ add_action( 'two-factor-user-options-' . __CLASS__, array( $this, 'user_options' ) );
32
  return parent::__construct();
33
  }
34
 
49
  * @param WP_User $user WP_User object of the logged-in user.
50
  */
51
  public function authentication_page( $user ) {
52
+ require_once( ABSPATH . '/wp-admin/includes/template.php' );
53
  ?>
54
  <p><?php esc_html_e( 'Are you really you?', 'two-factor' ); ?></p>
55
  <?php
providers/{class-two-factor-email.php → class.two-factor-email.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Class for creating an email provider.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Class for creating an email provider.
10
  *
@@ -17,17 +11,10 @@ class Two_Factor_Email extends Two_Factor_Provider {
17
  /**
18
  * The user meta token key.
19
  *
20
- * @var string
21
  */
22
  const TOKEN_META_KEY = '_two_factor_email_token';
23
 
24
- /**
25
- * Store the timestamp when the token was generated.
26
- *
27
- * @var string
28
- */
29
- const TOKEN_META_KEY_TIMESTAMP = '_two_factor_email_token_timestamp';
30
-
31
  /**
32
  * Name of the input field used for code resend.
33
  *
@@ -40,11 +27,11 @@ class Two_Factor_Email extends Two_Factor_Provider {
40
  *
41
  * @since 0.1-dev
42
  */
43
- public static function get_instance() {
44
  static $instance;
45
  $class = __CLASS__;
46
  if ( ! is_a( $instance, $class ) ) {
47
- $instance = new $class();
48
  }
49
  return $instance;
50
  }
@@ -55,7 +42,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
55
  * @since 0.1-dev
56
  */
57
  protected function __construct() {
58
- add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
59
  return parent::__construct();
60
  }
61
 
@@ -78,10 +65,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
78
  */
79
  public function generate_token( $user_id ) {
80
  $token = $this->get_code();
81
-
82
- update_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, time() );
83
  update_user_meta( $user_id, self::TOKEN_META_KEY, wp_hash( $token ) );
84
-
85
  return $token;
86
  }
87
 
@@ -96,65 +80,9 @@ class Two_Factor_Email extends Two_Factor_Provider {
96
 
97
  if ( ! empty( $hashed_token ) ) {
98
  return true;
99
- }
100
-
101
- return false;
102
- }
103
-
104
- /**
105
- * Has the user token validity timestamp expired.
106
- *
107
- * @param integer $user_id User ID.
108
- *
109
- * @return boolean
110
- */
111
- public function user_token_has_expired( $user_id ) {
112
- $token_lifetime = $this->user_token_lifetime( $user_id );
113
- $token_ttl = $this->user_token_ttl( $user_id );
114
-
115
- // Invalid token lifetime is considered an expired token.
116
- if ( is_int( $token_lifetime ) && $token_lifetime <= $token_ttl ) {
117
  return false;
118
  }
119
-
120
- return true;
121
- }
122
-
123
- /**
124
- * Get the lifetime of a user token in seconds.
125
- *
126
- * @param integer $user_id User ID.
127
- *
128
- * @return integer|null Return `null` if the lifetime can't be measured.
129
- */
130
- public function user_token_lifetime( $user_id ) {
131
- $timestamp = intval( get_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, true ) );
132
-
133
- if ( ! empty( $timestamp ) ) {
134
- return time() - $timestamp;
135
- }
136
-
137
- return null;
138
- }
139
-
140
- /**
141
- * Return the token time-to-live for a user.
142
- *
143
- * @param integer $user_id User ID.
144
- *
145
- * @return integer
146
- */
147
- public function user_token_ttl( $user_id ) {
148
- $token_ttl = 15 * MINUTE_IN_SECONDS;
149
-
150
- /**
151
- * Number of seconds the token is considered valid
152
- * after the generation.
153
- *
154
- * @param integer $token_ttl Token time-to-live in seconds.
155
- * @param integer $user_id User ID.
156
- */
157
- return (int) apply_filters( 'two_factor_token_ttl', $token_ttl, $user_id );
158
  }
159
 
160
  /**
@@ -191,11 +119,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
191
  return false;
192
  }
193
 
194
- if ( $this->user_token_has_expired( $user_id ) ) {
195
- return false;
196
- }
197
-
198
- // Ensure the token can be used only once.
199
  $this->delete_token( $user_id );
200
 
201
  return true;
@@ -228,23 +152,6 @@ class Two_Factor_Email extends Two_Factor_Provider {
228
  /* translators: %s: token */
229
  $message = wp_strip_all_tags( sprintf( __( 'Enter %s to log in.', 'two-factor' ), $token ) );
230
 
231
- /**
232
- * Filter the token email subject.
233
- *
234
- * @param string $subject The email subject line.
235
- * @param int $user_id The ID of the user.
236
- */
237
- $subject = apply_filters( 'two_factor_token_email_subject', $subject, $user->ID );
238
-
239
- /**
240
- * Filter the token email message.
241
- *
242
- * @param string $message The email message.
243
- * @param string $token The token.
244
- * @param int $user_id The ID of the user.
245
- */
246
- $message = apply_filters( 'two_factor_token_email_message', $message, $token, $user->ID );
247
-
248
  return wp_mail( $user->user_email, $subject, $message );
249
  }
250
 
@@ -260,16 +167,16 @@ class Two_Factor_Email extends Two_Factor_Provider {
260
  return;
261
  }
262
 
263
- if ( ! $this->user_has_token( $user->ID ) || $this->user_token_has_expired( $user->ID ) ) {
264
  $this->generate_and_email_token( $user );
265
  }
266
 
267
- require_once ABSPATH . '/wp-admin/includes/template.php';
268
  ?>
269
  <p><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p>
270
  <p>
271
  <label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label>
272
- <input type="tel" name="two-factor-email-code" id="authcode" class="input" value="" size="20" />
273
  <?php submit_button( __( 'Log In', 'two-factor' ) ); ?>
274
  </p>
275
  <p class="two-factor-email-resend">
@@ -317,10 +224,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
317
  return false;
318
  }
319
 
320
- // Ensure there are no spaces or line breaks around the code.
321
- $code = trim( sanitize_text_field( $_REQUEST['two-factor-email-code'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, handled by the core method already.
322
-
323
- return $this->validate_token( $user->ID, $code );
324
  }
325
 
326
  /**
@@ -347,13 +251,11 @@ class Two_Factor_Email extends Two_Factor_Provider {
347
  ?>
348
  <div>
349
  <?php
350
- echo esc_html(
351
- sprintf(
352
  /* translators: %s: email address */
353
- __( 'Authentication codes will be sent to %s.', 'two-factor' ),
354
- $email
355
- )
356
- );
357
  ?>
358
  </div>
359
  <?php
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Class for creating an email provider.
4
  *
11
  /**
12
  * The user meta token key.
13
  *
14
+ * @type string
15
  */
16
  const TOKEN_META_KEY = '_two_factor_email_token';
17
 
 
 
 
 
 
 
 
18
  /**
19
  * Name of the input field used for code resend.
20
  *
27
  *
28
  * @since 0.1-dev
29
  */
30
+ static function get_instance() {
31
  static $instance;
32
  $class = __CLASS__;
33
  if ( ! is_a( $instance, $class ) ) {
34
+ $instance = new $class;
35
  }
36
  return $instance;
37
  }
42
  * @since 0.1-dev
43
  */
44
  protected function __construct() {
45
+ add_action( 'two-factor-user-options-' . __CLASS__, array( $this, 'user_options' ) );
46
  return parent::__construct();
47
  }
48
 
65
  */
66
  public function generate_token( $user_id ) {
67
  $token = $this->get_code();
 
 
68
  update_user_meta( $user_id, self::TOKEN_META_KEY, wp_hash( $token ) );
 
69
  return $token;
70
  }
71
 
80
 
81
  if ( ! empty( $hashed_token ) ) {
82
  return true;
83
+ } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  return false;
85
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
 
88
  /**
119
  return false;
120
  }
121
 
122
+ // Ensure that the token can't be re-used.
 
 
 
 
123
  $this->delete_token( $user_id );
124
 
125
  return true;
152
  /* translators: %s: token */
153
  $message = wp_strip_all_tags( sprintf( __( 'Enter %s to log in.', 'two-factor' ), $token ) );
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  return wp_mail( $user->user_email, $subject, $message );
156
  }
157
 
167
  return;
168
  }
169
 
170
+ if ( ! $this->user_has_token( $user->ID ) ) {
171
  $this->generate_and_email_token( $user );
172
  }
173
 
174
+ require_once( ABSPATH . '/wp-admin/includes/template.php' );
175
  ?>
176
  <p><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p>
177
  <p>
178
  <label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label>
179
+ <input type="tel" name="two-factor-email-code" id="authcode" class="input" value="" size="20" pattern="[0-9]*" />
180
  <?php submit_button( __( 'Log In', 'two-factor' ) ); ?>
181
  </p>
182
  <p class="two-factor-email-resend">
224
  return false;
225
  }
226
 
227
+ return $this->validate_token( $user->ID, $_REQUEST['two-factor-email-code'] );
 
 
 
228
  }
229
 
230
  /**
251
  ?>
252
  <div>
253
  <?php
254
+ echo esc_html( sprintf(
 
255
  /* translators: %s: email address */
256
+ __( 'Authentication codes will be sent to %s.', 'two-factor' ),
257
+ $email
258
+ ) );
 
259
  ?>
260
  </div>
261
  <?php
providers/{class-two-factor-fido-u2f-admin-list-table.php → class.two-factor-fido-u2f-admin-list-table.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Class for displaying the list of security key items.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  // Load the parent class if it doesn't exist.
9
  if ( ! class_exists( 'WP_List_Table' ) ) {
10
  require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
@@ -30,7 +24,7 @@ class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table {
30
  public function get_columns() {
31
  return array(
32
  'name' => wp_strip_all_tags( __( 'Name', 'two-factor' ) ),
33
- 'added' => wp_strip_all_tags( __( 'Added', 'two-factor' ) ),
34
  'last_used' => wp_strip_all_tags( __( 'Last Used', 'two-factor' ) ),
35
  );
36
  }
@@ -41,10 +35,10 @@ class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table {
41
  * @since 0.1-dev
42
  */
43
  public function prepare_items() {
44
- $columns = $this->get_columns();
45
- $hidden = array();
46
- $sortable = array();
47
- $primary = 'name';
48
  $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
49
  }
50
 
@@ -61,20 +55,20 @@ class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table {
61
  protected function column_default( $item, $column_name ) {
62
  switch ( $column_name ) {
63
  case 'name':
64
- $out = '<div class="hidden" id="inline_' . esc_attr( $item->keyHandle ) . '">'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
65
  $out .= '<div class="name">' . esc_html( $item->name ) . '</div>';
66
  $out .= '</div>';
67
 
68
  $actions = array(
69
  'rename hide-if-no-js' => Two_Factor_FIDO_U2F_Admin::rename_link( $item ),
70
- 'delete' => Two_Factor_FIDO_U2F_Admin::delete_link( $item ),
71
  );
72
 
73
  return esc_html( $item->name ) . $out . self::row_actions( $actions );
74
  case 'added':
75
- return gmdate( get_option( 'date_format', 'r' ), $item->added );
76
  case 'last_used':
77
- return gmdate( get_option( 'date_format', 'r' ), $item->last_used );
78
  default:
79
  return 'WTF^^?';
80
  }
@@ -102,7 +96,7 @@ class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table {
102
  */
103
  public function single_row( $item ) {
104
  ?>
105
- <tr id="key-<?php echo esc_attr( $item->keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase ?>">
106
  <?php $this->single_row_columns( $item ); ?>
107
  </tr>
108
  <?php
@@ -121,6 +115,8 @@ class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table {
121
  <td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>" class="colspanchange">
122
  <fieldset>
123
  <div class="inline-edit-col">
 
 
124
  <label>
125
  <span class="title"><?php esc_html_e( 'Name', 'two-factor' ); ?></span>
126
  <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" /></span>
@@ -128,11 +124,7 @@ class Two_Factor_FIDO_U2F_Admin_List_Table extends WP_List_Table {
128
  </div>
129
  </fieldset>
130
  <?php
131
- $core_columns = array(
132
- 'name' => true,
133
- 'added' => true,
134
- 'last_used' => true,
135
- );
136
  list( $columns ) = $this->get_column_info();
137
  foreach ( $columns as $column_name => $column_display_name ) {
138
  if ( isset( $core_columns[ $column_name ] ) ) {
1
  <?php
 
 
 
 
 
 
2
  // Load the parent class if it doesn't exist.
3
  if ( ! class_exists( 'WP_List_Table' ) ) {
4
  require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
24
  public function get_columns() {
25
  return array(
26
  'name' => wp_strip_all_tags( __( 'Name', 'two-factor' ) ),
27
+ 'added' => wp_strip_all_tags( __( 'Added', 'two-factor' ) ),
28
  'last_used' => wp_strip_all_tags( __( 'Last Used', 'two-factor' ) ),
29
  );
30
  }
35
  * @since 0.1-dev
36
  */
37
  public function prepare_items() {
38
+ $columns = $this->get_columns();
39
+ $hidden = array();
40
+ $sortable = array();
41
+ $primary = 'name';
42
  $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
43
  }
44
 
55
  protected function column_default( $item, $column_name ) {
56
  switch ( $column_name ) {
57
  case 'name':
58
+ $out = '<div class="hidden" id="inline_' . esc_attr( $item->keyHandle ) . '">';
59
  $out .= '<div class="name">' . esc_html( $item->name ) . '</div>';
60
  $out .= '</div>';
61
 
62
  $actions = array(
63
  'rename hide-if-no-js' => Two_Factor_FIDO_U2F_Admin::rename_link( $item ),
64
+ 'delete' => Two_Factor_FIDO_U2F_Admin::delete_link( $item ),
65
  );
66
 
67
  return esc_html( $item->name ) . $out . self::row_actions( $actions );
68
  case 'added':
69
+ return date( get_option( 'date_format', 'r' ), $item->added );
70
  case 'last_used':
71
+ return date( get_option( 'date_format', 'r' ), $item->last_used );
72
  default:
73
  return 'WTF^^?';
74
  }
96
  */
97
  public function single_row( $item ) {
98
  ?>
99
+ <tr id="key-<?php echo esc_attr( $item->keyHandle ); ?>">
100
  <?php $this->single_row_columns( $item ); ?>
101
  </tr>
102
  <?php
115
  <td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>" class="colspanchange">
116
  <fieldset>
117
  <div class="inline-edit-col">
118
+ <h4><?php esc_html_e( 'Quick Edit', 'two-factor' ); ?></h4>
119
+
120
  <label>
121
  <span class="title"><?php esc_html_e( 'Name', 'two-factor' ); ?></span>
122
  <span class="input-text-wrap"><input type="text" name="name" class="ptitle" value="" /></span>
124
  </div>
125
  </fieldset>
126
  <?php
127
+ $core_columns = array( 'name' => true, 'added' => true, 'last_used' => true );
 
 
 
 
128
  list( $columns ) = $this->get_column_info();
129
  foreach ( $columns as $column_name => $column_display_name ) {
130
  if ( isset( $core_columns[ $column_name ] ) ) {
providers/{class-two-factor-fido-u2f-admin.php → class.two-factor-fido-u2f-admin.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Class for registering & modifying FIDO U2F security keys.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Class for registering & modifying FIDO U2F security keys.
10
  *
@@ -30,13 +24,13 @@ class Two_Factor_FIDO_U2F_Admin {
30
  * @static
31
  */
32
  public static function add_hooks() {
33
- add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
34
  add_action( 'show_user_security_settings', array( __CLASS__, 'show_user_profile' ) );
35
- add_action( 'personal_options_update', array( __CLASS__, 'catch_submission' ), 0 );
36
- add_action( 'edit_user_profile_update', array( __CLASS__, 'catch_submission' ), 0 );
37
- add_action( 'load-profile.php', array( __CLASS__, 'catch_delete_security_key' ) );
38
- add_action( 'load-user-edit.php', array( __CLASS__, 'catch_delete_security_key' ) );
39
- add_action( 'wp_ajax_inline-save-key', array( __CLASS__, 'wp_ajax_inline_save' ) );
40
  }
41
 
42
  /**
@@ -50,20 +44,16 @@ class Two_Factor_FIDO_U2F_Admin {
50
  * @param string $hook Current page.
51
  */
52
  public static function enqueue_assets( $hook ) {
53
- if ( ! in_array( $hook, array( 'user-edit.php', 'profile.php' ), true ) ) {
54
- return;
55
- }
56
-
57
- $user_id = Two_Factor_Core::current_user_being_edited();
58
- if ( ! $user_id ) {
59
  return;
60
  }
61
 
 
62
  $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id );
63
 
64
- // @todo Ensure that scripts don't fail because of missing u2fL10n.
65
  try {
66
- $data = Two_Factor_FIDO_U2F::$u2f->getRegisterData( $security_keys );
67
  list( $req,$sigs ) = $data;
68
 
69
  update_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, $req );
@@ -91,15 +81,14 @@ class Two_Factor_FIDO_U2F_Admin {
91
  */
92
 
93
  $translation_array = array(
94
- 'user_id' => $user_id,
95
  'register' => array(
96
  'request' => $req,
97
- 'sigs' => $sigs,
98
  ),
99
- 'text' => array(
100
- 'insert' => esc_html__( 'Now insert (and tap) your Security Key.', 'two-factor' ),
101
- 'error' => esc_html__( 'U2F request failed.', 'two-factor' ),
102
- 'error_codes' => array(
103
  // Map u2f.ErrorCodes to error messages.
104
  0 => esc_html__( 'Request OK.', 'two-factor' ),
105
  1 => esc_html__( 'Other U2F error.', 'two-factor' ),
@@ -208,8 +197,8 @@ class Two_Factor_FIDO_U2F_Admin {
208
  <p><a href="https://support.google.com/accounts/answer/6103523"><?php esc_html_e( 'You can find FIDO U2F Security Key devices for sale from here.', 'two-factor' ); ?></a></p>
209
 
210
  <?php
211
- require TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin-list-table.php';
212
- $u2f_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table();
213
  $u2f_list_table->items = $security_keys;
214
  $u2f_list_table->prepare_items();
215
  $u2f_list_table->display();
@@ -238,7 +227,7 @@ class Two_Factor_FIDO_U2F_Admin {
238
 
239
  try {
240
  $response = json_decode( stripslashes( $_POST['u2f_response'] ) );
241
- $reg = Two_Factor_FIDO_U2F::$u2f->doRegister( get_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, true ), $response );
242
  $reg->new = true;
243
 
244
  Two_Factor_FIDO_U2F::add_security_key( $user_id, $reg );
@@ -248,14 +237,9 @@ class Two_Factor_FIDO_U2F_Admin {
248
 
249
  delete_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY );
250
 
251
- wp_safe_redirect(
252
- add_query_arg(
253
- array(
254
- 'new_app_pass' => 1,
255
- ),
256
- wp_get_referer()
257
- ) . '#security-keys-section'
258
- );
259
  exit;
260
  }
261
  }
@@ -271,11 +255,9 @@ class Two_Factor_FIDO_U2F_Admin {
271
  * @static
272
  */
273
  public static function catch_delete_security_key() {
274
- $user_id = Two_Factor_Core::current_user_being_edited();
275
-
276
- if ( ! empty( $user_id ) && ! empty( $_REQUEST['delete_security_key'] ) ) {
277
  $slug = $_REQUEST['delete_security_key'];
278
-
279
  check_admin_referer( "delete_security_key-{$slug}", '_nonce_delete_security_key' );
280
 
281
  Two_Factor_FIDO_U2F::delete_security_key( $user_id, $slug );
@@ -311,7 +293,7 @@ class Two_Factor_FIDO_U2F_Admin {
311
  * @return string
312
  */
313
  public static function delete_link( $item ) {
314
- $delete_link = add_query_arg( 'delete_security_key', $item->keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
315
  $delete_link = wp_nonce_url( $delete_link, "delete_security_key-{$item->keyHandle}", '_nonce_delete_security_key' );
316
  return sprintf( '<a href="%1$s">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Delete', 'two-factor' ) );
317
  }
@@ -327,21 +309,22 @@ class Two_Factor_FIDO_U2F_Admin {
327
  public static function wp_ajax_inline_save() {
328
  check_ajax_referer( 'keyinlineeditnonce', '_inline_edit' );
329
 
330
- require TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin-list-table.php';
331
  $wp_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table();
332
 
333
  if ( ! isset( $_POST['keyHandle'] ) ) {
334
  wp_die();
335
  }
336
 
337
- $user_id = Two_Factor_Core::current_user_being_edited();
 
338
  $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id );
339
  if ( ! $security_keys ) {
340
  wp_die();
341
  }
342
 
343
  foreach ( $security_keys as &$key ) {
344
- if ( $key->keyHandle === $_POST['keyHandle'] ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
345
  break;
346
  }
347
  }
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Class for registering & modifying FIDO U2F security keys.
4
  *
24
  * @static
25
  */
26
  public static function add_hooks() {
27
+ add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
28
  add_action( 'show_user_security_settings', array( __CLASS__, 'show_user_profile' ) );
29
+ add_action( 'personal_options_update', array( __CLASS__, 'catch_submission' ), 0 );
30
+ add_action( 'edit_user_profile_update', array( __CLASS__, 'catch_submission' ), 0 );
31
+ add_action( 'load-profile.php', array( __CLASS__, 'catch_delete_security_key' ) );
32
+ add_action( 'load-user-edit.php', array( __CLASS__, 'catch_delete_security_key' ) );
33
+ add_action( 'wp_ajax_inline-save-key', array( __CLASS__, 'wp_ajax_inline_save' ) );
34
  }
35
 
36
  /**
44
  * @param string $hook Current page.
45
  */
46
  public static function enqueue_assets( $hook ) {
47
+ if ( ! in_array( $hook, array( 'user-edit.php', 'profile.php' ) ) ) {
 
 
 
 
 
48
  return;
49
  }
50
 
51
+ $user_id = get_current_user_id();
52
  $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id );
53
 
54
+ // @todo Ensure that scripts don't fail because of missing u2fL10n
55
  try {
56
+ $data = Two_Factor_FIDO_U2F::$u2f->getRegisterData( $security_keys );
57
  list( $req,$sigs ) = $data;
58
 
59
  update_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, $req );
81
  */
82
 
83
  $translation_array = array(
 
84
  'register' => array(
85
  'request' => $req,
86
+ 'sigs' => $sigs,
87
  ),
88
+ 'text' => array(
89
+ 'insert' => esc_html__( 'Now insert (and tap) your Security Key.', 'two-factor' ),
90
+ 'error' => esc_html__( 'U2F request failed.', 'two-factor' ),
91
+ 'error_codes' => array(
92
  // Map u2f.ErrorCodes to error messages.
93
  0 => esc_html__( 'Request OK.', 'two-factor' ),
94
  1 => esc_html__( 'Other U2F error.', 'two-factor' ),
197
  <p><a href="https://support.google.com/accounts/answer/6103523"><?php esc_html_e( 'You can find FIDO U2F Security Key devices for sale from here.', 'two-factor' ); ?></a></p>
198
 
199
  <?php
200
+ require( TWO_FACTOR_DIR . 'providers/class.two-factor-fido-u2f-admin-list-table.php' );
201
+ $u2f_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table();
202
  $u2f_list_table->items = $security_keys;
203
  $u2f_list_table->prepare_items();
204
  $u2f_list_table->display();
227
 
228
  try {
229
  $response = json_decode( stripslashes( $_POST['u2f_response'] ) );
230
+ $reg = Two_Factor_FIDO_U2F::$u2f->doRegister( get_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, true ), $response );
231
  $reg->new = true;
232
 
233
  Two_Factor_FIDO_U2F::add_security_key( $user_id, $reg );
237
 
238
  delete_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY );
239
 
240
+ wp_safe_redirect( add_query_arg( array(
241
+ 'new_app_pass' => 1,
242
+ ), wp_get_referer() ) . '#security-keys-section' );
 
 
 
 
 
243
  exit;
244
  }
245
  }
255
  * @static
256
  */
257
  public static function catch_delete_security_key() {
258
+ $user_id = get_current_user_id();
259
+ if ( ! empty( $_REQUEST['delete_security_key'] ) ) {
 
260
  $slug = $_REQUEST['delete_security_key'];
 
261
  check_admin_referer( "delete_security_key-{$slug}", '_nonce_delete_security_key' );
262
 
263
  Two_Factor_FIDO_U2F::delete_security_key( $user_id, $slug );
293
  * @return string
294
  */
295
  public static function delete_link( $item ) {
296
+ $delete_link = add_query_arg( 'delete_security_key', $item->keyHandle );
297
  $delete_link = wp_nonce_url( $delete_link, "delete_security_key-{$item->keyHandle}", '_nonce_delete_security_key' );
298
  return sprintf( '<a href="%1$s">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Delete', 'two-factor' ) );
299
  }
309
  public static function wp_ajax_inline_save() {
310
  check_ajax_referer( 'keyinlineeditnonce', '_inline_edit' );
311
 
312
+ require( TWO_FACTOR_DIR . 'providers/class.two-factor-fido-u2f-admin-list-table.php' );
313
  $wp_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table();
314
 
315
  if ( ! isset( $_POST['keyHandle'] ) ) {
316
  wp_die();
317
  }
318
 
319
+ $user_id = get_current_user_id();
320
+
321
  $security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id );
322
  if ( ! $security_keys ) {
323
  wp_die();
324
  }
325
 
326
  foreach ( $security_keys as &$key ) {
327
+ if ( $key->keyHandle === $_POST['keyHandle'] ) {
328
  break;
329
  }
330
  }
providers/{class-two-factor-fido-u2f.php → class.two-factor-fido-u2f.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Class for creating a FIDO Universal 2nd Factor provider.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Class for creating a FIDO Universal 2nd Factor provider.
10
  *
@@ -40,14 +34,14 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
40
  *
41
  * @var string
42
  */
43
- const U2F_ASSET_VERSION = '0.2.1';
44
 
45
  /**
46
  * Ensures only one instance of this class exists in memory at any one time.
47
  *
48
  * @return \Two_Factor_FIDO_U2F
49
  */
50
- public static function get_instance() {
51
  static $instance;
52
 
53
  if ( ! isset( $instance ) ) {
@@ -67,18 +61,29 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
67
  return;
68
  }
69
 
70
- require_once TWO_FACTOR_DIR . 'includes/Yubico/U2F.php';
71
  self::$u2f = new u2flib_server\U2F( self::get_u2f_app_id() );
72
 
73
- require_once TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin.php';
74
  Two_Factor_FIDO_U2F_Admin::add_hooks();
75
 
76
- // Ensure the script dependencies have been registered before they're enqueued at a later priority.
77
- add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 5 );
78
- add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 5 );
79
- add_action( 'login_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 5 );
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
82
 
83
  return parent::__construct();
84
  }
@@ -116,31 +121,16 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
116
  * @since 0.1-dev
117
  */
118
  public function get_label() {
119
- return _x( 'FIDO U2F Security Keys', 'Provider Label', 'two-factor' );
120
  }
121
 
122
  /**
123
- * Register script dependencies used during login and when
124
- * registering keys in the WP admin.
125
  *
126
- * @return void
127
  */
128
- public static function enqueue_scripts() {
129
- wp_register_script(
130
- 'fido-u2f-api',
131
- plugins_url( 'includes/Google/u2f-api.js', dirname( __FILE__ ) ),
132
- null,
133
- self::asset_version(),
134
- true
135
- );
136
-
137
- wp_register_script(
138
- 'fido-u2f-login',
139
- plugins_url( 'js/fido-u2f-login.js', __FILE__ ),
140
- array( 'jquery', 'fido-u2f-api' ),
141
- self::asset_version(),
142
- true
143
- );
144
  }
145
 
146
  /**
@@ -152,9 +142,9 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
152
  * @return null
153
  */
154
  public function authentication_page( $user ) {
155
- require_once ABSPATH . '/wp-admin/includes/template.php';
156
 
157
- // U2F doesn't work without HTTPS.
158
  if ( ! is_ssl() ) {
159
  ?>
160
  <p><?php esc_html_e( 'U2F requires an HTTPS connection. Please use an alternative 2nd factor method.', 'two-factor' ); ?></p>
@@ -208,7 +198,7 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
208
  try {
209
  $reg = self::$u2f->doAuthenticate( $requests, $keys, $response );
210
 
211
- $reg->last_used = time();
212
 
213
  self::update_security_key( $user->ID, $reg );
214
 
@@ -261,8 +251,8 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
261
 
262
  if (
263
  ! is_object( $register )
264
- || ! property_exists( $register, 'keyHandle' ) || empty( $register->keyHandle ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
265
- || ! property_exists( $register, 'publicKey' ) || empty( $register->publicKey ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
266
  || ! property_exists( $register, 'certificate' ) || empty( $register->certificate )
267
  || ! property_exists( $register, 'counter' ) || ( -1 > $register->counter )
268
  ) {
@@ -270,14 +260,14 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
270
  }
271
 
272
  $register = array(
273
- 'keyHandle' => $register->keyHandle, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
274
- 'publicKey' => $register->publicKey, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
275
  'certificate' => $register->certificate,
276
  'counter' => $register->counter,
277
  );
278
 
279
  $register['name'] = __( 'New Security Key', 'two-factor' );
280
- $register['added'] = time();
281
  $register['last_used'] = $register['added'];
282
 
283
  return add_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY, $register );
@@ -328,8 +318,8 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
328
 
329
  if (
330
  ! is_object( $data )
331
- || ! property_exists( $data, 'keyHandle' ) || empty( $data->keyHandle ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
332
- || ! property_exists( $data, 'publicKey' ) || empty( $data->publicKey ) // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
333
  || ! property_exists( $data, 'certificate' ) || empty( $data->certificate )
334
  || ! property_exists( $data, 'counter' ) || ( -1 > $data->counter )
335
  ) {
@@ -339,7 +329,7 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
339
  $keys = self::get_security_keys( $user_id );
340
  if ( $keys ) {
341
  foreach ( $keys as $key ) {
342
- if ( $key->keyHandle === $data->keyHandle ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
343
  return update_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY, (array) $data, (array) $key );
344
  }
345
  }
@@ -357,7 +347,7 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
357
  * @param string $keyHandle Optional. Key handle.
358
  * @return bool True on success, false on failure.
359
  */
360
- public static function delete_security_key( $user_id, $keyHandle = null ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
361
  global $wpdb;
362
 
363
  if ( ! is_numeric( $user_id ) ) {
@@ -369,21 +359,18 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
369
  return false;
370
  }
371
 
372
- $keyHandle = wp_unslash( $keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
373
- $keyHandle = maybe_serialize( $keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
374
 
375
- $query = $wpdb->prepare( "SELECT umeta_id FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id = %d", self::REGISTERED_KEY_USER_META_KEY, $user_id );
 
376
 
377
- if ( $keyHandle ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
378
- $key_handle_lookup = sprintf( ':"%s";s:', $keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
379
 
380
- $query .= $wpdb->prepare(
381
- ' AND meta_value LIKE %s',
382
- '%' . $wpdb->esc_like( $key_handle_lookup ) . '%'
383
- );
384
  }
385
 
386
- $meta_ids = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
387
  if ( ! count( $meta_ids ) ) {
388
  return false;
389
  }
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Class for creating a FIDO Universal 2nd Factor provider.
4
  *
34
  *
35
  * @var string
36
  */
37
+ const U2F_ASSET_VERSION = '0.2.0';
38
 
39
  /**
40
  * Ensures only one instance of this class exists in memory at any one time.
41
  *
42
  * @return \Two_Factor_FIDO_U2F
43
  */
44
+ static function get_instance() {
45
  static $instance;
46
 
47
  if ( ! isset( $instance ) ) {
61
  return;
62
  }
63
 
64
+ require_once( TWO_FACTOR_DIR . 'includes/Yubico/U2F.php' );
65
  self::$u2f = new u2flib_server\U2F( self::get_u2f_app_id() );
66
 
67
+ require_once( TWO_FACTOR_DIR . 'providers/class.two-factor-fido-u2f-admin.php' );
68
  Two_Factor_FIDO_U2F_Admin::add_hooks();
69
 
70
+ wp_register_script(
71
+ 'fido-u2f-api',
72
+ plugins_url( 'includes/Google/u2f-api.js', dirname( __FILE__ ) ),
73
+ null,
74
+ self::asset_version(),
75
+ true
76
+ );
77
+
78
+ wp_register_script(
79
+ 'fido-u2f-login',
80
+ plugins_url( 'js/fido-u2f-login.js', __FILE__ ),
81
+ array( 'jquery', 'fido-u2f-api' ),
82
+ self::asset_version(),
83
+ true
84
+ );
85
 
86
+ add_action( 'two-factor-user-options-' . __CLASS__, array( $this, 'user_options' ) );
87
 
88
  return parent::__construct();
89
  }
121
  * @since 0.1-dev
122
  */
123
  public function get_label() {
124
+ return _x( 'FIDO Universal 2nd Factor (U2F)', 'Provider Label', 'two-factor' );
125
  }
126
 
127
  /**
128
+ * Enqueue assets for login form.
 
129
  *
130
+ * @since 0.1-dev
131
  */
132
+ public function login_enqueue_assets() {
133
+ wp_enqueue_script( 'fido-u2f-login' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  }
135
 
136
  /**
142
  * @return null
143
  */
144
  public function authentication_page( $user ) {
145
+ require_once( ABSPATH . '/wp-admin/includes/template.php' );
146
 
147
+ // U2F doesn't work without HTTPS
148
  if ( ! is_ssl() ) {
149
  ?>
150
  <p><?php esc_html_e( 'U2F requires an HTTPS connection. Please use an alternative 2nd factor method.', 'two-factor' ); ?></p>
198
  try {
199
  $reg = self::$u2f->doAuthenticate( $requests, $keys, $response );
200
 
201
+ $reg->last_used = current_time( 'timestamp' );
202
 
203
  self::update_security_key( $user->ID, $reg );
204
 
251
 
252
  if (
253
  ! is_object( $register )
254
+ || ! property_exists( $register, 'keyHandle' ) || empty( $register->keyHandle )
255
+ || ! property_exists( $register, 'publicKey' ) || empty( $register->publicKey )
256
  || ! property_exists( $register, 'certificate' ) || empty( $register->certificate )
257
  || ! property_exists( $register, 'counter' ) || ( -1 > $register->counter )
258
  ) {
260
  }
261
 
262
  $register = array(
263
+ 'keyHandle' => $register->keyHandle,
264
+ 'publicKey' => $register->publicKey,
265
  'certificate' => $register->certificate,
266
  'counter' => $register->counter,
267
  );
268
 
269
  $register['name'] = __( 'New Security Key', 'two-factor' );
270
+ $register['added'] = current_time( 'timestamp' );
271
  $register['last_used'] = $register['added'];
272
 
273
  return add_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY, $register );
318
 
319
  if (
320
  ! is_object( $data )
321
+ || ! property_exists( $data, 'keyHandle' ) || empty( $data->keyHandle )
322
+ || ! property_exists( $data, 'publicKey' ) || empty( $data->publicKey )
323
  || ! property_exists( $data, 'certificate' ) || empty( $data->certificate )
324
  || ! property_exists( $data, 'counter' ) || ( -1 > $data->counter )
325
  ) {
329
  $keys = self::get_security_keys( $user_id );
330
  if ( $keys ) {
331
  foreach ( $keys as $key ) {
332
+ if ( $key->keyHandle === $data->keyHandle ) {
333
  return update_user_meta( $user_id, self::REGISTERED_KEY_USER_META_KEY, (array) $data, (array) $key );
334
  }
335
  }
347
  * @param string $keyHandle Optional. Key handle.
348
  * @return bool True on success, false on failure.
349
  */
350
+ public static function delete_security_key( $user_id, $keyHandle = null ) {
351
  global $wpdb;
352
 
353
  if ( ! is_numeric( $user_id ) ) {
359
  return false;
360
  }
361
 
362
+ $table = $wpdb->usermeta;
 
363
 
364
+ $keyHandle = wp_unslash( $keyHandle );
365
+ $keyHandle = maybe_serialize( $keyHandle );
366
 
367
+ $query = $wpdb->prepare( "SELECT umeta_id FROM $table WHERE meta_key = '%s' AND user_id = %d", self::REGISTERED_KEY_USER_META_KEY, $user_id );
 
368
 
369
+ if ( $keyHandle ) {
370
+ $query .= $wpdb->prepare( ' AND meta_value LIKE %s', '%:"' . $keyHandle . '";s:%' );
 
 
371
  }
372
 
373
+ $meta_ids = $wpdb->get_col( $query );
374
  if ( ! count( $meta_ids ) ) {
375
  return false;
376
  }
providers/{class-two-factor-provider.php → class.two-factor-provider.php} RENAMED
@@ -1,10 +1,4 @@
1
  <?php
2
- /**
3
- * Abstract class for creating two factor authentication providers.
4
- *
5
- * @package Two_Factor
6
- */
7
-
8
  /**
9
  * Abstract class for creating two factor authentication providers.
10
  *
@@ -30,7 +24,7 @@ abstract class Two_Factor_Provider {
30
  *
31
  * @return string
32
  */
33
- abstract public function get_label();
34
 
35
  /**
36
  * Prints the name of the provider.
@@ -48,7 +42,7 @@ abstract class Two_Factor_Provider {
48
  *
49
  * @param WP_User $user WP_User object of the logged-in user.
50
  */
51
- abstract public function authentication_page( $user );
52
 
53
  /**
54
  * Allow providers to do extra processing before the authentication.
@@ -70,7 +64,7 @@ abstract class Two_Factor_Provider {
70
  * @param WP_User $user WP_User object of the logged-in user.
71
  * @return boolean
72
  */
73
- abstract public function validate_authentication( $user );
74
 
75
  /**
76
  * Whether this Two Factor provider is configured and available for the user specified.
@@ -78,7 +72,7 @@ abstract class Two_Factor_Provider {
78
  * @param WP_User $user WP_User object of the logged-in user.
79
  * @return boolean
80
  */
81
- abstract public function is_available_for_user( $user );
82
 
83
  /**
84
  * Generate a random eight-digit string to send out as an auth code.
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Abstract class for creating two factor authentication providers.
4
  *
24
  *
25
  * @return string
26
  */
27
+ abstract function get_label();
28
 
29
  /**
30
  * Prints the name of the provider.
42
  *
43
  * @param WP_User $user WP_User object of the logged-in user.
44
  */
45
+ abstract function authentication_page( $user );
46
 
47
  /**
48
  * Allow providers to do extra processing before the authentication.
64
  * @param WP_User $user WP_User object of the logged-in user.
65
  * @return boolean
66
  */
67
+ abstract function validate_authentication( $user );
68
 
69
  /**
70
  * Whether this Two Factor provider is configured and available for the user specified.
72
  * @param WP_User $user WP_User object of the logged-in user.
73
  * @return boolean
74
  */
75
+ abstract function is_available_for_user( $user );
76
 
77
  /**
78
  * Generate a random eight-digit string to send out as an auth code.
providers/{class-two-factor-totp.php → class.two-factor-totp.php} RENAMED
@@ -24,35 +24,20 @@ class Two_Factor_Totp extends Two_Factor_Provider {
24
  */
25
  const NOTICES_META_KEY = '_two_factor_totp_notices';
26
 
27
- /**
28
- * Action name for resetting the secret token.
29
- *
30
- * @var string
31
- */
32
- const ACTION_SECRET_DELETE = 'totp-delete';
33
-
34
- const DEFAULT_KEY_BIT_SIZE = 160;
35
- const DEFAULT_CRYPTO = 'sha1';
36
- const DEFAULT_DIGIT_COUNT = 6;
37
- const DEFAULT_TIME_STEP_SEC = 30;
38
  const DEFAULT_TIME_STEP_ALLOWANCE = 4;
39
-
40
- /**
41
- * Chracters used in base32 encoding.
42
- *
43
- * @var string
44
- */
45
- private static $base_32_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
46
 
47
  /**
48
  * Class constructor. Sets up hooks, etc.
49
  */
50
  protected function __construct() {
51
- add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_two_factor_options' ) );
52
- add_action( 'personal_options_update', array( $this, 'user_two_factor_options_update' ) );
53
- add_action( 'edit_user_profile_update', array( $this, 'user_two_factor_options_update' ) );
54
- add_action( 'two_factor_user_settings_action', array( $this, 'user_settings_action' ), 10, 2 );
55
-
56
  return parent::__construct();
57
  }
58
 
@@ -71,32 +56,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
71
  * Returns the name of the provider.
72
  */
73
  public function get_label() {
74
- return _x( 'Time Based One-Time Password (TOTP)', 'Provider Label', 'two-factor' );
75
- }
76
-
77
- /**
78
- * Trigger our custom user settings actions.
79
- *
80
- * @param integer $user_id User ID.
81
- * @param string $action Action ID.
82
- *
83
- * @return void
84
- */
85
- public function user_settings_action( $user_id, $action ) {
86
- if ( self::ACTION_SECRET_DELETE === $action ) {
87
- $this->delete_user_totp_key( $user_id );
88
- }
89
- }
90
-
91
- /**
92
- * Get the URL for deleting the secret token.
93
- *
94
- * @param integer $user_id User ID.
95
- *
96
- * @return string
97
- */
98
- protected function get_token_delete_url_for_user( $user_id ) {
99
- return Two_Factor_Core::get_user_update_action_url( $user_id, self::ACTION_SECRET_DELETE );
100
  }
101
 
102
  /**
@@ -113,21 +73,19 @@ class Two_Factor_Totp extends Two_Factor_Provider {
113
  wp_nonce_field( 'user_two_factor_totp_options', '_nonce_user_two_factor_totp_options', false );
114
 
115
  $key = $this->get_user_totp_key( $user->ID );
116
- $this->admin_notices( $user->ID );
117
 
118
  ?>
119
  <div id="two-factor-totp-options">
120
- <?php
121
- if ( empty( $key ) ) :
122
- $key = $this->generate_key();
123
- $site_name = get_bloginfo( 'name', 'display' );
124
- $totp_title = apply_filters( 'two_factor_totp_title', $site_name . ':' . $user->user_login, $user );
125
  ?>
126
  <p>
127
  <?php esc_html_e( 'Please scan the QR code or manually enter the key, then enter an authentication code from your app in order to complete setup.', 'two-factor' ); ?>
128
  </p>
129
  <p>
130
- <img src="<?php echo esc_url( $this->get_google_qr_code( $totp_title, $key, $site_name ) ); ?>" id="two-factor-totp-qrcode" />
131
  </p>
132
  <p>
133
  <code><?php echo esc_html( $key ); ?></code>
@@ -142,10 +100,10 @@ class Two_Factor_Totp extends Two_Factor_Provider {
142
  </p>
143
  <?php else : ?>
144
  <p class="success">
145
- <?php esc_html_e( 'Secret key is configured and registered. It is not possible to view it again for security reasons.', 'two-factor' ); ?>
146
  </p>
147
  <p>
148
- <a class="button" href="<?php echo esc_url( self::get_token_delete_url_for_user( $user->ID ) ); ?>"><?php esc_html_e( 'Reset Key', 'two-factor' ); ?></a>
149
  <em class="description">
150
  <?php esc_html_e( 'You will have to re-scan the QR code on all devices as the previous codes will stop working.', 'two-factor' ); ?>
151
  </em>
@@ -159,25 +117,27 @@ class Two_Factor_Totp extends Two_Factor_Provider {
159
  * Save the options specified in `::user_two_factor_options()`
160
  *
161
  * @param integer $user_id The user ID whose options are being updated.
162
- *
163
- * @return void
164
  */
165
  public function user_two_factor_options_update( $user_id ) {
166
  $notices = array();
167
- $errors = array();
 
 
168
 
169
  if ( isset( $_POST['_nonce_user_two_factor_totp_options'] ) ) {
170
  check_admin_referer( 'user_two_factor_totp_options', '_nonce_user_two_factor_totp_options' );
171
 
 
 
 
 
 
172
  // Validate and store a new secret key.
173
  if ( ! empty( $_POST['two-factor-totp-authcode'] ) && ! empty( $_POST['two-factor-totp-key'] ) ) {
174
- // Don't use filter_input() because we can't mock it during tests for now.
175
- $authcode = filter_var( sanitize_text_field( $_POST['two-factor-totp-authcode'] ), FILTER_SANITIZE_NUMBER_INT );
176
- $key = sanitize_text_field( $_POST['two-factor-totp-key'] );
177
-
178
- if ( $this->is_valid_key( $key ) ) {
179
- if ( $this->is_valid_authcode( $key, $authcode ) ) {
180
- if ( ! $this->set_user_totp_key( $user_id, $key ) ) {
181
  $errors[] = __( 'Unable to save Two Factor Authentication code. Please re-scan the QR code and enter the code provided by your application.', 'two-factor' );
182
  }
183
  } else {
@@ -240,7 +200,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
240
  * @return boolean
241
  */
242
  public function is_valid_key( $key ) {
243
- $check = sprintf( '/^[%s]+$/', self::$base_32_chars );
244
 
245
  if ( 1 === preg_match( $check, $key ) ) {
246
  return true;
@@ -251,20 +211,15 @@ class Two_Factor_Totp extends Two_Factor_Provider {
251
 
252
  /**
253
  * Display any available admin notices.
254
- *
255
- * @param integer $user_id User ID.
256
- *
257
- * @return void
258
  */
259
- public function admin_notices( $user_id ) {
260
- $notices = get_user_meta( $user_id, self::NOTICES_META_KEY, true );
261
 
262
  if ( ! empty( $notices ) ) {
263
- delete_user_meta( $user_id, self::NOTICES_META_KEY );
264
-
265
  foreach ( $notices as $class => $messages ) {
266
  ?>
267
- <div class="<?php echo esc_attr( $class ); ?>">
268
  <?php
269
  foreach ( $messages as $msg ) {
270
  ?>
@@ -288,10 +243,10 @@ class Two_Factor_Totp extends Two_Factor_Provider {
288
  * @return bool Whether the user gave a valid code
289
  */
290
  public function validate_authentication( $user ) {
291
- if ( ! empty( $_REQUEST['authcode'] ) ) {
292
  return $this->is_valid_authcode(
293
  $this->get_user_totp_key( $user->ID ),
294
- sanitize_text_field( $_REQUEST['authcode'] )
295
  );
296
  }
297
 
@@ -313,12 +268,9 @@ class Two_Factor_Totp extends Two_Factor_Provider {
313
  * Ticks are the allowed offset from the correct time in 30 second increments,
314
  * so the default of 4 allows codes that are two minutes to either side of server time
315
  *
316
- * @deprecated 0.7.0 Use {@see 'two_factor_totp_time_step_allowance'} instead.
317
  * @param int $max_ticks Max ticks of time correction to allow. Default 4.
318
  */
319
- $max_ticks = apply_filters_deprecated( 'two-factor-totp-time-step-allowance', array( self::DEFAULT_TIME_STEP_ALLOWANCE ), '0.7.0', 'two_factor_totp_time_step_allowance' );
320
-
321
- $max_ticks = apply_filters( 'two_factor_totp_time_step_allowance', self::DEFAULT_TIME_STEP_ALLOWANCE );
322
 
323
  // Array of all ticks to allow, sorted using absolute value to test closest match first.
324
  $ticks = range( - $max_ticks, $max_ticks );
@@ -343,7 +295,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
343
  * @return string $bitsize long string composed of available base32 chars.
344
  */
345
  public static function generate_key( $bitsize = self::DEFAULT_KEY_BIT_SIZE ) {
346
- $bytes = ceil( $bitsize / 8 );
347
  $secret = wp_generate_password( $bytes, true, true );
348
 
349
  return self::base32_encode( $secret );
@@ -373,8 +325,8 @@ class Two_Factor_Totp extends Two_Factor_Provider {
373
  $higher = 0;
374
  }
375
 
376
- $lowmap = 0xffffffff;
377
- $lower = $value & $lowmap;
378
 
379
  return pack( 'NN', $higher, $lower );
380
  }
@@ -424,7 +376,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
424
  */
425
  public static function get_google_qr_code( $name, $key, $title = null ) {
426
  // Encode to support spaces, question marks and other characters.
427
- $name = rawurlencode( $name );
428
  $google_url = urlencode( 'otpauth://totp/' . $name . '?secret=' . $key );
429
  if ( isset( $title ) ) {
430
  $google_url .= urlencode( '&issuer=' . rawurlencode( $title ) );
@@ -452,20 +404,18 @@ class Two_Factor_Totp extends Two_Factor_Provider {
452
  * @param WP_User $user WP_User object of the logged-in user.
453
  */
454
  public function authentication_page( $user ) {
455
- require_once ABSPATH . '/wp-admin/includes/template.php';
456
  ?>
457
- <p>
458
- <?php esc_html_e( 'Please enter the code generated by your authenticator app.', 'two-factor' ); ?>
459
- </p>
460
  <p>
461
  <label for="authcode"><?php esc_html_e( 'Authentication Code:', 'two-factor' ); ?></label>
462
- <input type="tel" autocomplete="off" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" />
463
  </p>
464
  <script type="text/javascript">
465
  setTimeout( function(){
466
  var d;
467
  try{
468
  d = document.getElementById('authcode');
 
469
  d.focus();
470
  } catch(e){}
471
  }, 200);
@@ -493,10 +443,10 @@ class Two_Factor_Totp extends Two_Factor_Provider {
493
  }
494
 
495
  $five_bit_sections = str_split( $binary_string, 5 );
496
- $base32_string = '';
497
 
498
  foreach ( $five_bit_sections as $five_bit_section ) {
499
- $base32_string .= self::$base_32_chars[ base_convert( str_pad( $five_bit_section, 5, '0' ), 2, 10 ) ];
500
  }
501
 
502
  return $base32_string;
@@ -513,25 +463,25 @@ class Two_Factor_Totp extends Two_Factor_Provider {
513
  */
514
  public static function base32_decode( $base32_string ) {
515
 
516
- $base32_string = strtoupper( $base32_string );
517
 
518
- if ( ! preg_match( '/^[' . self::$base_32_chars . ']+$/', $base32_string, $match ) ) {
519
  throw new Exception( 'Invalid characters in the base32 string.' );
520
  }
521
 
522
- $l = strlen( $base32_string );
523
- $n = 0;
524
- $j = 0;
525
  $binary = '';
526
 
527
  for ( $i = 0; $i < $l; $i++ ) {
528
 
529
- $n = $n << 5; // Move buffer left by 5 to make room.
530
- $n = $n + strpos( self::$base_32_chars, $base32_string[ $i ] ); // Add value into buffer.
531
  $j += 5; // Keep track of number of bits in buffer.
532
 
533
  if ( $j >= 8 ) {
534
- $j -= 8;
535
  $binary .= chr( ( $n & ( 0xFF << $j ) ) >> $j );
536
  }
537
  }
@@ -553,6 +503,6 @@ class Two_Factor_Totp extends Two_Factor_Provider {
553
  if ( $a === $b ) {
554
  return 0;
555
  }
556
- return ( $a < $b ) ? -1 : 1;
557
  }
558
  }
24
  */
25
  const NOTICES_META_KEY = '_two_factor_totp_notices';
26
 
27
+ const DEFAULT_KEY_BIT_SIZE = 160;
28
+ const DEFAULT_CRYPTO = 'sha1';
29
+ const DEFAULT_DIGIT_COUNT = 6;
30
+ const DEFAULT_TIME_STEP_SEC = 30;
 
 
 
 
 
 
 
31
  const DEFAULT_TIME_STEP_ALLOWANCE = 4;
32
+ private static $_base_32_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
 
 
 
 
 
 
33
 
34
  /**
35
  * Class constructor. Sets up hooks, etc.
36
  */
37
  protected function __construct() {
38
+ add_action( 'two-factor-user-options-' . __CLASS__, array( $this, 'user_two_factor_options' ) );
39
+ add_action( 'personal_options_update', array( $this, 'user_two_factor_options_update' ) );
40
+ add_action( 'edit_user_profile_update', array( $this, 'user_two_factor_options_update' ) );
 
 
41
  return parent::__construct();
42
  }
43
 
56
  * Returns the name of the provider.
57
  */
58
  public function get_label() {
59
+ return _x( 'Time Based One-Time Password (Google Authenticator)', 'Provider Label', 'two-factor' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
61
 
62
  /**
73
  wp_nonce_field( 'user_two_factor_totp_options', '_nonce_user_two_factor_totp_options', false );
74
 
75
  $key = $this->get_user_totp_key( $user->ID );
76
+ $this->admin_notices();
77
 
78
  ?>
79
  <div id="two-factor-totp-options">
80
+ <?php if ( empty( $key ) ) :
81
+ $key = $this->generate_key();
82
+ $site_name = get_bloginfo( 'name', 'display' );
 
 
83
  ?>
84
  <p>
85
  <?php esc_html_e( 'Please scan the QR code or manually enter the key, then enter an authentication code from your app in order to complete setup.', 'two-factor' ); ?>
86
  </p>
87
  <p>
88
+ <img src="<?php echo esc_url( $this->get_google_qr_code( $site_name . ':' . $user->user_login, $key, $site_name ) ); ?>" id="two-factor-totp-qrcode" />
89
  </p>
90
  <p>
91
  <code><?php echo esc_html( $key ); ?></code>
100
  </p>
101
  <?php else : ?>
102
  <p class="success">
103
+ <?php esc_html_e( 'Secret key configured and registered.', 'two-factor' ); ?>
104
  </p>
105
  <p>
106
+ <input type="submit" class="button" name="two-factor-totp-delete" value="<?php esc_attr_e( 'Reset Key', 'two-factor' ); ?>" />
107
  <em class="description">
108
  <?php esc_html_e( 'You will have to re-scan the QR code on all devices as the previous codes will stop working.', 'two-factor' ); ?>
109
  </em>
117
  * Save the options specified in `::user_two_factor_options()`
118
  *
119
  * @param integer $user_id The user ID whose options are being updated.
120
+ * @return false
 
121
  */
122
  public function user_two_factor_options_update( $user_id ) {
123
  $notices = array();
124
+ $errors = array();
125
+
126
+ $current_key = $this->get_user_totp_key( $user_id );
127
 
128
  if ( isset( $_POST['_nonce_user_two_factor_totp_options'] ) ) {
129
  check_admin_referer( 'user_two_factor_totp_options', '_nonce_user_two_factor_totp_options' );
130
 
131
+ // Delete the secret key.
132
+ if ( ! empty( $current_key ) && isset( $_POST['two-factor-totp-delete'] ) ) {
133
+ $this->delete_user_totp_key( $user_id );
134
+ }
135
+
136
  // Validate and store a new secret key.
137
  if ( ! empty( $_POST['two-factor-totp-authcode'] ) && ! empty( $_POST['two-factor-totp-key'] ) ) {
138
+ if ( $this->is_valid_key( $_POST['two-factor-totp-key'] ) ) {
139
+ if ( $this->is_valid_authcode( $_POST['two-factor-totp-key'], $_POST['two-factor-totp-authcode'] ) ) {
140
+ if ( ! $this->set_user_totp_key( $user_id, $_POST['two-factor-totp-key'] ) ) {
 
 
 
 
141
  $errors[] = __( 'Unable to save Two Factor Authentication code. Please re-scan the QR code and enter the code provided by your application.', 'two-factor' );
142
  }
143
  } else {
200
  * @return boolean
201
  */
202
  public function is_valid_key( $key ) {
203
+ $check = sprintf( '/^[%s]+$/', self::$_base_32_chars );
204
 
205
  if ( 1 === preg_match( $check, $key ) ) {
206
  return true;
211
 
212
  /**
213
  * Display any available admin notices.
 
 
 
 
214
  */
215
+ public function admin_notices() {
216
+ $notices = get_user_meta( get_current_user_id(), self::NOTICES_META_KEY, true );
217
 
218
  if ( ! empty( $notices ) ) {
219
+ delete_user_meta( get_current_user_id(), self::NOTICES_META_KEY );
 
220
  foreach ( $notices as $class => $messages ) {
221
  ?>
222
+ <div class="<?php echo esc_attr( $class ) ?>">
223
  <?php
224
  foreach ( $messages as $msg ) {
225
  ?>
243
  * @return bool Whether the user gave a valid code
244
  */
245
  public function validate_authentication( $user ) {
246
+ if ( ! empty( $_REQUEST['authcode'] ) ) { // WPCS: CSRF ok, nonce verified by login_form_validate_2fa().
247
  return $this->is_valid_authcode(
248
  $this->get_user_totp_key( $user->ID ),
249
+ sanitize_text_field( $_REQUEST['authcode'] ) // WPCS: CSRF ok, nonce verified by login_form_validate_2fa().
250
  );
251
  }
252
 
268
  * Ticks are the allowed offset from the correct time in 30 second increments,
269
  * so the default of 4 allows codes that are two minutes to either side of server time
270
  *
 
271
  * @param int $max_ticks Max ticks of time correction to allow. Default 4.
272
  */
273
+ $max_ticks = apply_filters( 'two-factor-totp-time-step-allowance', self::DEFAULT_TIME_STEP_ALLOWANCE );
 
 
274
 
275
  // Array of all ticks to allow, sorted using absolute value to test closest match first.
276
  $ticks = range( - $max_ticks, $max_ticks );
295
  * @return string $bitsize long string composed of available base32 chars.
296
  */
297
  public static function generate_key( $bitsize = self::DEFAULT_KEY_BIT_SIZE ) {
298
+ $bytes = ceil( $bitsize / 8 );
299
  $secret = wp_generate_password( $bytes, true, true );
300
 
301
  return self::base32_encode( $secret );
325
  $higher = 0;
326
  }
327
 
328
+ $lowmap = 0xffffffff;
329
+ $lower = $value & $lowmap;
330
 
331
  return pack( 'NN', $higher, $lower );
332
  }
376
  */
377
  public static function get_google_qr_code( $name, $key, $title = null ) {
378
  // Encode to support spaces, question marks and other characters.
379
+ $name = rawurlencode( $name );
380
  $google_url = urlencode( 'otpauth://totp/' . $name . '?secret=' . $key );
381
  if ( isset( $title ) ) {
382
  $google_url .= urlencode( '&issuer=' . rawurlencode( $title ) );
404
  * @param WP_User $user WP_User object of the logged-in user.
405
  */
406
  public function authentication_page( $user ) {
407
+ require_once( ABSPATH . '/wp-admin/includes/template.php' );
408
  ?>
 
 
 
409
  <p>
410
  <label for="authcode"><?php esc_html_e( 'Authentication Code:', 'two-factor' ); ?></label>
411
+ <input type="tel" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" />
412
  </p>
413
  <script type="text/javascript">
414
  setTimeout( function(){
415
  var d;
416
  try{
417
  d = document.getElementById('authcode');
418
+ d.value = '';
419
  d.focus();
420
  } catch(e){}
421
  }, 200);
443
  }
444
 
445
  $five_bit_sections = str_split( $binary_string, 5 );
446
+ $base32_string = '';
447
 
448
  foreach ( $five_bit_sections as $five_bit_section ) {
449
+ $base32_string .= self::$_base_32_chars[ base_convert( str_pad( $five_bit_section, 5, '0' ), 2, 10 ) ];
450
  }
451
 
452
  return $base32_string;
463
  */
464
  public static function base32_decode( $base32_string ) {
465
 
466
+ $base32_string = strtoupper( $base32_string );
467
 
468
+ if ( ! preg_match( '/^[' . self::$_base_32_chars . ']+$/', $base32_string, $match ) ) {
469
  throw new Exception( 'Invalid characters in the base32 string.' );
470
  }
471
 
472
+ $l = strlen( $base32_string );
473
+ $n = 0;
474
+ $j = 0;
475
  $binary = '';
476
 
477
  for ( $i = 0; $i < $l; $i++ ) {
478
 
479
+ $n = $n << 5; // Move buffer left by 5 to make room.
480
+ $n = $n + strpos( self::$_base_32_chars, $base32_string[ $i ] ); // Add value into buffer.
481
  $j += 5; // Keep track of number of bits in buffer.
482
 
483
  if ( $j >= 8 ) {
484
+ $j -= 8;
485
  $binary .= chr( ( $n & ( 0xFF << $j ) ) >> $j );
486
  }
487
  }
503
  if ( $a === $b ) {
504
  return 0;
505
  }
506
+ return ($a < $b) ? -1 : 1;
507
  }
508
  }
providers/js/fido-u2f-admin-inline-edit.js CHANGED
@@ -80,8 +80,7 @@ var inlineEditKey;
80
 
81
  params = {
82
  action: 'inline-save-key',
83
- keyHandle: id,
84
- user_id: window.u2fL10n.user_id
85
  };
86
 
87
  fields = $( '#edit-' + id ).find( ':input' ).serialize();
80
 
81
  params = {
82
  action: 'inline-save-key',
83
+ keyHandle: id
 
84
  };
85
 
86
  fields = $( '#edit-' + id ).find( ':input' ).serialize();
readme.md CHANGED
@@ -4,61 +4,30 @@
4
  ![Banner](assets/banner-1544x500.png)
5
  Enable Two-Factor Authentication using time-based one-time passwords (OTP, Google Authenticator), Universal 2nd Factor (FIDO U2F, YubiKey), email and backup verification codes.
6
 
7
- **Contributors:** [georgestephanis](https://profiles.wordpress.org/georgestephanis), [valendesigns](https://profiles.wordpress.org/valendesigns), [stevenkword](https://profiles.wordpress.org/stevenkword), [extendwings](https://profiles.wordpress.org/extendwings), [sgrant](https://profiles.wordpress.org/sgrant), [aaroncampbell](https://profiles.wordpress.org/aaroncampbell), [johnbillion](https://profiles.wordpress.org/johnbillion), [stevegrunwell](https://profiles.wordpress.org/stevegrunwell), [netweb](https://profiles.wordpress.org/netweb), [kasparsd](https://profiles.wordpress.org/kasparsd), [alihusnainarshad](https://profiles.wordpress.org/alihusnainarshad), [passoniate](https://profiles.wordpress.org/passoniate)
8
  **Tags:** [two factor](https://wordpress.org/plugins/tags/two-factor), [two step](https://wordpress.org/plugins/tags/two-step), [authentication](https://wordpress.org/plugins/tags/authentication), [login](https://wordpress.org/plugins/tags/login), [totp](https://wordpress.org/plugins/tags/totp), [fido u2f](https://wordpress.org/plugins/tags/fido-u2f), [u2f](https://wordpress.org/plugins/tags/u2f), [email](https://wordpress.org/plugins/tags/email), [backup codes](https://wordpress.org/plugins/tags/backup-codes), [2fa](https://wordpress.org/plugins/tags/2fa), [yubikey](https://wordpress.org/plugins/tags/yubikey)
9
  **Requires at least:** 4.3
10
- **Tested up to:** 5.5
11
  **Stable tag:** trunk (master)
12
- **Requires PHP:** 5.6
13
 
14
  [![Build Status](https://travis-ci.org/WordPress/two-factor.svg?branch=master)](https://travis-ci.org/WordPress/two-factor) [![Coverage Status](https://coveralls.io/repos/WordPress/two-factor/badge.svg?branch=master)](https://coveralls.io/github/WordPress/two-factor) [![Built with Grunt](https://gruntjs.com/cdn/builtwith.svg)](http://gruntjs.com)
15
 
16
  ## Description ##
17
 
18
- Use the "Two-Factor Options" section under "Users" → "Your Profile" to enable and configure one or multiple two-factor authentication providers for your account:
19
-
20
- - Email codes
21
- - Time Based One-Time Passwords (TOTP)
22
- - FIDO Universal 2nd Factor (U2F)
23
- - Backup Codes
24
- - Dummy Method (only for testing purposes)
25
-
26
- For more history, see [this post](https://georgestephanis.wordpress.com/2013/08/14/two-cents-on-two-factor/).
27
- ### Actions & Filters ###
28
- Here is a list of action and filter hooks provided by the plugin:
29
-
30
- - `two_factor_providers` filter overrides the available two-factor providers such as email and time-based one-time passwords. Array values are PHP classnames of the two-factor providers.
31
- - `two_factor_enabled_providers_for_user` filter overrides the list of two-factor providers enabled for a user. First argument is an array of enabled provider classnames as values, the second argument is the user ID.
32
- - `two_factor_user_authenticated` action which receives the logged in `WP_User` object as the first argument for determining the logged in user right after the authentication workflow.
33
- - `two_factor_token_ttl` filter overrides the time interval in seconds that an email token is considered after generation. Accepts the time in seconds as the first argument and the ID of the `WP_User` object being authenticated.
34
 
 
35
 
36
- ## Screenshots ##
37
-
38
- ### Two-factor options under User Profile.
39
-
40
- ![Two-factor options under User Profile.](assets/screenshot-1.png)
41
-
42
- ### U2F Security Keys section under User Profile.
43
-
44
- ![U2F Security Keys section under User Profile.](assets/screenshot-2.png)
45
-
46
- ### Email Code Authentication during WordPress Login.
47
 
48
- ![Email Code Authentication during WordPress Login.](assets/screenshot-3.png)
49
 
50
- ## Get Involved ##
 
51
 
52
- Development happens [on GitHub](https://github.com/wordpress/two-factor/). Join the `#core-passwords` channel [on WordPress Slack](http://wordpress.slack.com) ([sign up here](http://chat.wordpress.org)).
53
-
54
- Here is how to get started:
55
-
56
- $ git clone https://github.com/wordpress/two-factor.git
57
- $ npm install
58
-
59
  Then open [a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) with the suggested changes.
60
 
61
  ## Changelog ##
62
 
63
- See the [release history](https://github.com/wordpress/two-factor/releases).
64
 
4
  ![Banner](assets/banner-1544x500.png)
5
  Enable Two-Factor Authentication using time-based one-time passwords (OTP, Google Authenticator), Universal 2nd Factor (FIDO U2F, YubiKey), email and backup verification codes.
6
 
7
+ **Contributors:** [georgestephanis](https://profiles.wordpress.org/georgestephanis), [valendesigns](https://profiles.wordpress.org/valendesigns), [stevenkword](https://profiles.wordpress.org/stevenkword), [extendwings](https://profiles.wordpress.org/extendwings), [sgrant](https://profiles.wordpress.org/sgrant), [aaroncampbell](https://profiles.wordpress.org/aaroncampbell), [johnbillion](https://profiles.wordpress.org/johnbillion), [stevegrunwell](https://profiles.wordpress.org/stevegrunwell), [netweb](https://profiles.wordpress.org/netweb), [kasparsd](https://profiles.wordpress.org/kasparsd)
8
  **Tags:** [two factor](https://wordpress.org/plugins/tags/two-factor), [two step](https://wordpress.org/plugins/tags/two-step), [authentication](https://wordpress.org/plugins/tags/authentication), [login](https://wordpress.org/plugins/tags/login), [totp](https://wordpress.org/plugins/tags/totp), [fido u2f](https://wordpress.org/plugins/tags/fido-u2f), [u2f](https://wordpress.org/plugins/tags/u2f), [email](https://wordpress.org/plugins/tags/email), [backup codes](https://wordpress.org/plugins/tags/backup-codes), [2fa](https://wordpress.org/plugins/tags/2fa), [yubikey](https://wordpress.org/plugins/tags/yubikey)
9
  **Requires at least:** 4.3
10
+ **Tested up to:** 5.1
11
  **Stable tag:** trunk (master)
 
12
 
13
  [![Build Status](https://travis-ci.org/WordPress/two-factor.svg?branch=master)](https://travis-ci.org/WordPress/two-factor) [![Coverage Status](https://coveralls.io/repos/WordPress/two-factor/badge.svg?branch=master)](https://coveralls.io/github/WordPress/two-factor) [![Built with Grunt](https://gruntjs.com/cdn/builtwith.svg)](http://gruntjs.com)
14
 
15
  ## Description ##
16
 
17
+ For more history, see [this post](https://stephanis.info/2013/08/14/two-cents-on-two-factor/).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ ## Get Involved ##
20
 
21
+ Development happens [on GitHub](https://github.com/georgestephanis/two-factor/). Join the `#core-passwords` channel [on WordPress Slack](http://wordpress.slack.com) ([sign up here](http://chat.wordpress.org)).
 
 
 
 
 
 
 
 
 
 
22
 
23
+ Here is how to get started:
24
 
25
+ $ git clone https://github.com/georgestephanis/two-factor.git
26
+ $ npm install
27
 
 
 
 
 
 
 
 
28
  Then open [a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) with the suggested changes.
29
 
30
  ## Changelog ##
31
 
32
+ See the [release history](https://github.com/georgestephanis/two-factor/releases).
33
 
readme.txt CHANGED
@@ -1,51 +1,27 @@
1
- === Two-Factor ===
2
- Contributors: georgestephanis, valendesigns, stevenkword, extendwings, sgrant, aaroncampbell, johnbillion, stevegrunwell, netweb, kasparsd, alihusnainarshad, passoniate
3
- Tags: two factor, two step, authentication, login, totp, fido u2f, u2f, email, backup codes, 2fa, yubikey
4
- Requires at least: 4.3
5
- Tested up to: 5.5
6
- Requires PHP: 5.6
7
- Stable tag: trunk
8
-
9
- Enable Two-Factor Authentication using time-based one-time passwords (OTP, Google Authenticator), Universal 2nd Factor (FIDO U2F, YubiKey), email and backup verification codes.
10
-
11
- == Description ==
12
-
13
- Use the "Two-Factor Options" section under "Users" → "Your Profile" to enable and configure one or multiple two-factor authentication providers for your account:
14
-
15
- - Email codes
16
- - Time Based One-Time Passwords (TOTP)
17
- - FIDO Universal 2nd Factor (U2F)
18
- - Backup Codes
19
- - Dummy Method (only for testing purposes)
20
-
21
- For more history, see [this post](https://georgestephanis.wordpress.com/2013/08/14/two-cents-on-two-factor/).
22
-
23
- = Actions & Filters =
24
-
25
- Here is a list of action and filter hooks provided by the plugin:
26
-
27
- - `two_factor_providers` filter overrides the available two-factor providers such as email and time-based one-time passwords. Array values are PHP classnames of the two-factor providers.
28
- - `two_factor_enabled_providers_for_user` filter overrides the list of two-factor providers enabled for a user. First argument is an array of enabled provider classnames as values, the second argument is the user ID.
29
- - `two_factor_user_authenticated` action which receives the logged in `WP_User` object as the first argument for determining the logged in user right after the authentication workflow.
30
- - `two_factor_token_ttl` filter overrides the time interval in seconds that an email token is considered after generation. Accepts the time in seconds as the first argument and the ID of the `WP_User` object being authenticated.
31
-
32
- == Screenshots ==
33
-
34
- 1. Two-factor options under User Profile.
35
- 2. U2F Security Keys section under User Profile.
36
- 3. Email Code Authentication during WordPress Login.
37
-
38
- == Get Involved ==
39
-
40
- Development happens [on GitHub](https://github.com/wordpress/two-factor/). Join the `#core-passwords` channel [on WordPress Slack](http://wordpress.slack.com) ([sign up here](http://chat.wordpress.org)).
41
-
42
- Here is how to get started:
43
-
44
- $ git clone https://github.com/wordpress/two-factor.git
45
- $ npm install
46
-
47
- Then open [a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) with the suggested changes.
48
-
49
- == Changelog ==
50
-
51
- See the [release history](https://github.com/wordpress/two-factor/releases).
1
+ === Two-Factor ===
2
+ Contributors: georgestephanis, valendesigns, stevenkword, extendwings, sgrant, aaroncampbell, johnbillion, stevegrunwell, netweb, kasparsd
3
+ Tags: two factor, two step, authentication, login, totp, fido u2f, u2f, email, backup codes, 2fa, yubikey
4
+ Requires at least: 4.3
5
+ Tested up to: 5.1
6
+ Stable tag: trunk
7
+
8
+ Enable Two-Factor Authentication using time-based one-time passwords (OTP, Google Authenticator), Universal 2nd Factor (FIDO U2F, YubiKey), email and backup verification codes.
9
+
10
+ == Description ==
11
+
12
+ For more history, see [this post](https://stephanis.info/2013/08/14/two-cents-on-two-factor/).
13
+
14
+ == Get Involved ==
15
+
16
+ Development happens [on GitHub](https://github.com/georgestephanis/two-factor/). Join the `#core-passwords` channel [on WordPress Slack](http://wordpress.slack.com) ([sign up here](http://chat.wordpress.org)).
17
+
18
+ Here is how to get started:
19
+
20
+ $ git clone https://github.com/georgestephanis/two-factor.git
21
+ $ npm install
22
+
23
+ Then open [a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) with the suggested changes.
24
+
25
+ == Changelog ==
26
+
27
+ See the [release history](https://github.com/georgestephanis/two-factor/releases).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
two-factor.php CHANGED
@@ -1,19 +1,11 @@
1
  <?php
2
  /**
3
- * Two Factor
4
- *
5
- * @package Two_Factor
6
- * @author Plugin Contributors
7
- * @copyright 2020 Plugin Contributors
8
- * @license GPL-2.0-or-later
9
- *
10
- * @wordpress-plugin
11
  * Plugin Name: Two Factor
12
  * Plugin URI: https://wordpress.org/plugins/two-factor/
13
- * Description: Two-Factor Authentication using time-based one-time passwords, Universal 2nd Factor (FIDO U2F), email and backup verification codes.
14
  * Author: Plugin Contributors
15
- * Version: 0.7.0
16
- * Author URI: https://github.com/wordpress/two-factor/graphs/contributors
17
  * Network: True
18
  * Text Domain: two-factor
19
  */
@@ -23,26 +15,14 @@
23
  */
24
  define( 'TWO_FACTOR_DIR', plugin_dir_path( __FILE__ ) );
25
 
26
- /**
27
- * Version of the plugin.
28
- */
29
- define( 'TWO_FACTOR_VERSION', '0.7.0' );
30
-
31
  /**
32
  * Include the base class here, so that other plugins can also extend it.
33
  */
34
- require_once TWO_FACTOR_DIR . 'providers/class-two-factor-provider.php';
35
 
36
  /**
37
  * Include the core that handles the common bits.
38
  */
39
- require_once TWO_FACTOR_DIR . 'class-two-factor-core.php';
40
-
41
- /**
42
- * A compatability layer for some of the most-used plugins out there.
43
- */
44
- require_once TWO_FACTOR_DIR . 'class-two-factor-compat.php';
45
-
46
- $two_factor_compat = new Two_Factor_Compat();
47
 
48
- Two_Factor_Core::add_hooks( $two_factor_compat );
1
  <?php
2
  /**
 
 
 
 
 
 
 
 
3
  * Plugin Name: Two Factor
4
  * Plugin URI: https://wordpress.org/plugins/two-factor/
5
+ * Description: A prototype extensible core to enable Two-Factor Authentication.
6
  * Author: Plugin Contributors
7
+ * Version: 0.4.5
8
+ * Author URI: https://github.com/georgestephanis/two-factor/graphs/contributors
9
  * Network: True
10
  * Text Domain: two-factor
11
  */
15
  */
16
  define( 'TWO_FACTOR_DIR', plugin_dir_path( __FILE__ ) );
17
 
 
 
 
 
 
18
  /**
19
  * Include the base class here, so that other plugins can also extend it.
20
  */
21
+ require_once( TWO_FACTOR_DIR . 'providers/class.two-factor-provider.php' );
22
 
23
  /**
24
  * Include the core that handles the common bits.
25
  */
26
+ require_once( TWO_FACTOR_DIR . 'class.two-factor-core.php' );
 
 
 
 
 
 
 
27
 
28
+ Two_Factor_Core::add_hooks();