PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.9.2
WP 2FA – Two-factor authentication for WordPress v2.9.2
1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / wp-2fa.php
wp-2fa Last commit date
dist 10 months ago includes 10 months ago languages 10 months ago vendor 10 months ago .DS_Store 10 months ago index.php 10 months ago license.txt 10 months ago readme.txt 10 months ago wp-2fa.php 10 months ago
wp-2fa.php
306 lines
1 <?php
2 /**
3 * WP 2FA - Two-factor authentication for WordPress .
4 *
5 * @copyright Copyright (C) 2013-2025, Melapress - support@melapress.com
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 or higher
7 *
8 * @wordpress-plugin
9 * Plugin Name: WP 2FA - Two-factor authentication for WordPress
10 * Version: 2.9.2
11 * Plugin URI: https://melapress.com/
12 * Description: Easily add an additional layer of security to your WordPress login pages. Enable Two-Factor Authentication for you and all your website users with this easy to use plugin.
13 * Author: Melapress
14 * Author URI: https://melapress.com/
15 * Text Domain: wp-2fa
16 * Domain Path: /languages/
17 * License: GPL v3
18 * Requires at least: 5.5
19 * Requires PHP: 7.4
20 * Network: true
21 *
22 * @package WP2FA
23 *
24 * This program is free software: you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation, either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <http://www.gnu.org/licenses/>.
36 *
37 * @fs_ignore /dist/, /extensions/, /freemius/, /includes/, /languages/, /third-party/, /vendor/
38 */
39
40 use WP2FA\WP2FA;
41 use WP2FA\Utils\Migration;
42 use WP2FA\Extensions_Loader;
43 use WP2FA\Admin\Helpers\WP_Helper;
44 use WP2FA\Freemius\Freemius_Helper;
45 use WP2FA\Admin\Helpers\File_Writer;
46
47 if ( ! defined( 'ABSPATH' ) ) {
48 exit;
49 }
50
51 if ( defined( '\DISABLE_2FA_LOGIN' ) && \DISABLE_2FA_LOGIN ) {
52 return;
53 }
54
55 // Useful global constants.
56 if ( ! defined( 'WP_2FA_VERSION' ) ) {
57 define( 'WP_2FA_VERSION', '2.9.2' );
58 define( 'WP_2FA_BASE', plugin_basename( __FILE__ ) );
59 define( 'WP_2FA_URL', plugin_dir_url( __FILE__ ) );
60 define( 'WP_2FA_PATH', WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( WP_2FA_BASE ) . DIRECTORY_SEPARATOR );
61 define( 'WP_2FA_INC', WP_2FA_PATH . 'includes/' );
62 define( 'WP_2FA_FILE', __FILE__ );
63 define( 'WP_2FA_LOGS_DIR', 'wp-2fa-logs' );
64
65 // Prefix used in usermetas, settings and transients.
66 define( 'WP_2FA_PREFIX', 'wp_2fa_' );
67 define( 'WP_2FA_POLICY_SETTINGS_NAME', WP_2FA_PREFIX . 'policy' );
68 define( 'WP_2FA_SETTINGS_NAME', WP_2FA_PREFIX . 'settings' );
69 define( 'WP_2FA_WHITE_LABEL_SETTINGS_NAME', WP_2FA_PREFIX . 'white_label' );
70 define( 'WP_2FA_EMAIL_SETTINGS_NAME', WP_2FA_PREFIX . 'email_settings' );
71
72 define( 'WP_2FA_PREFIX_PAGE', 'wp-2fa-' );
73
74 define( 'WP_2FA_TEXTDOMAIN', 'wp-2fa' );
75 }
76
77 // phpcs:disable
78 // phpcs:enable
79 // Include files.
80 require_once WP_2FA_INC . 'functions/core.php';
81
82 // Require Composer autoloader if it exists.
83 if ( file_exists( WP_2FA_PATH . 'vendor/autoload.php' ) ) {
84 require_once WP_2FA_PATH . 'vendor/autoload.php';
85 }
86
87 // run any required update routines.
88 Migration::migrate();
89
90 // Setup_Wizard.
91 if ( WP_Helper::is_multisite() ) {
92 \add_action( 'network_admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'network_admin_menus' ), 10 );
93 \add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
94 } else {
95 \add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
96 }
97
98 // Activation/Deactivation.
99 \register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
100 \register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
101 // Register our uninstallation hook.
102 \register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
103
104 \add_filter( 'plugins_loaded', array( WP2FA::class, 'init' ) );
105 \add_action( 'plugins_loaded', array( WP2FA::class, 'add_wizard_actions' ), 10 );
106
107
108 // phpcs:disable
109 // phpcs:enable
110
111 if ( ! defined( File_Writer::SECRET_NAME ) ) {
112 define( File_Writer::SECRET_NAME, WP2FA::get_secret_key() );
113
114 define( 'WP2FA_SECRET_IS_IN_DB', true );
115 }
116
117 // phpcs:disable
118 /* @free:start */
119 // phpcs:enable
120 if ( ! function_exists( 'wp2fa_free_on_plugin_activation' ) ) {
121 /**
122 * Takes care of deactivation of the premium plugin when the free plugin is activated.
123 *
124 * Note: This code MUST NOT be present in the premium version an is removed automatically during the build process.
125 *
126 * @since 2.0.0
127 */
128 function wp2fa_free_on_plugin_activation() {
129 $premium_version_slug = 'wp-2fa-premium/wp-2fa.php';
130 if ( is_plugin_active( $premium_version_slug ) ) {
131 deactivate_plugins( $premium_version_slug, true );
132 }
133 check_ssl();
134 }
135
136 \register_activation_hook( __FILE__, 'wp2fa_free_on_plugin_activation' );
137 }
138 // phpcs:disable
139 /* @free:end */
140 // phpcs:enable
141
142 /*
143 * Clears the config cache from the DB
144 *
145 * @return void
146 *
147 * @since 2.2.0
148 */
149 \add_action(
150 'upgrader_process_complete',
151 function () {
152 delete_transient( 'wp_2fa_config_file_hash' );
153 },
154 10,
155 2
156 );
157
158 \add_action( 'doing_it_wrong_trigger_error', 'wp_2fa_trigger_error', 10, 4 );
159 \add_action( 'doing_it_wrong_run', 'wp_2fa_action_doing_it_wrong_run', 0, 3 );
160 \add_action( 'doing_it_wrong_run', 'wp_2fa_action_doing_it_wrong_run', 20, 3 );
161
162 if ( ! function_exists( 'wp_2f_is_just_in_time_for_2fa_domain' ) ) {
163 /**
164 * Whether it is the just_in_time_error for wp-2fa domains.
165 *
166 * @since 2.9.0
167 *
168 * @param string $status Status of the error.
169 * @param string $function_name Function name.
170 * @param string $message Message.
171 *
172 * @return bool
173 */
174 function wp_2f_is_just_in_time_for_2fa_domain( $status, string $function_name, string $message ): bool {
175
176 return '_load_textdomain_just_in_time' === $function_name && strpos( $message, '<code>' . WP_2FA_TEXTDOMAIN ) !== false;
177 }
178 }
179
180 if ( ! function_exists( 'wp_2fa_trigger_error' ) ) {
181 /**
182 * Catches errors which come from the doing_it_wrong() function, WP core does not provide much information about what is really going on and where, this method adds some more information to the error log.
183 *
184 * @param bool $status - Whether to trigger the error for _doing_it_wrong() calls. Default true.
185 * @param string $function_name - The name of the function that triggered the error (this is the WP function which is not called right, not the real function that actually called it).
186 * @param string $errstr - The WP error string (message).
187 * @param string $version - Since which WP version given error was added.
188 * @param int $errno - The number of the error (type of the error - that probably never get set by WP and always falls to the default which is E_USER_NOTICE).
189 *
190 * @return bool
191 *
192 * @since 2.9.0
193 */
194 function wp_2fa_trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) {
195
196 if ( false === $status ) {
197 return $status;
198 }
199
200 if ( wp_2f_is_just_in_time_for_2fa_domain( '', $function_name, $errstr ) ) {
201 // This error code is not included in error_reporting, so let it fall.
202 // through to the standard PHP error handler.
203 return false;
204 }
205 }
206 }
207
208 if ( ! function_exists( 'wp_2fa_action_doing_it_wrong_run' ) ) {
209 /**
210 * Action for _doing_it_wrong() calls.
211 *
212 * @since 2.9.0
213 *
214 * @param string $function_name The function that was called.
215 * @param string $message A message explaining what has been done incorrectly.
216 * @param string $version The version of WordPress where the message was added.
217 *
218 * @return void
219 */
220 function wp_2fa_action_doing_it_wrong_run( $function_name, $message, $version ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
221
222 global $wp_filter;
223
224 $function_name = (string) $function_name;
225 $message = (string) $message;
226
227 if ( ! class_exists( '\QM_Collectors', false ) || ! wp_2f_is_just_in_time_for_2fa_domain( '', $function_name, $message ) ) {
228 return;
229 }
230
231 $qm_collector_doing_it_wrong = \QM_Collectors::get( 'doing_it_wrong' );
232 $current_priority = $wp_filter['doing_it_wrong_run']->current_priority();
233
234 if ( null === $qm_collector_doing_it_wrong || false === $current_priority ) {
235 return;
236 }
237
238 switch ( $current_priority ) {
239 case 0:
240 \remove_action( 'doing_it_wrong_run', array( $qm_collector_doing_it_wrong, 'action_doing_it_wrong_run' ) );
241 break;
242
243 case 20:
244 \add_action( 'doing_it_wrong_run', array( $qm_collector_doing_it_wrong, 'action_doing_it_wrong_run' ), 10, 3 );
245 break;
246
247 default:
248 break;
249 }
250 }
251 }
252
253 if ( ! function_exists( 'check_ssl' ) ) {
254 /**
255 * Checks if the required library is installed and cancels the process if not.
256 *
257 * @return void
258 *
259 * @since 2.2.0
260 */
261 function check_ssl() {
262 if ( ! \WP2FA\Authenticator\Open_SSL::is_ssl_available() ) {
263 $html = '<div class="updated notice is-dismissible">
264 <p>' . \esc_html__( 'This plugin requires OpenSSL. Contact your web host or website administrator so they can enable OpenSSL. Re-activate the plugin once the library has been enabled.', 'wp-2fa' )
265 . '</p>
266 </div>';
267
268 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
269
270 exit();
271 }
272 }
273 }
274
275 if ( \PHP_VERSION_ID < 80000 && ! \interface_exists( 'Stringable' ) ) {
276 // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed
277 interface Stringable {
278 /**
279 * Mockup function for PHP versions lower than 8.
280 *
281 * @return string
282 */
283 public function __toString();
284 }
285 }
286
287 if ( ! function_exists( 'str_starts_with' ) ) {
288 /**
289 * PHP lower than 8 is missing that function but it required in the newer versions of our plugin.
290 *
291 * @param string $haystack - The string to search in.
292 * @param string $needle - The needle to search for.
293 *
294 * @return bool
295 *
296 * @since 2.6.4
297 */
298 function str_starts_with( $haystack, $needle ): bool {
299 if ( '' === $needle ) {
300 return true;
301 }
302
303 return 0 === strpos( $haystack, $needle );
304 }
305 }
306